jupiter-dynamic-forms 1.20.6 â 1.20.8
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/core/add-column-dialog.d.ts +31 -2
- package/dist/core/add-column-dialog.d.ts.map +1 -1
- package/dist/core/dynamic-form.d.ts +17 -0
- package/dist/core/dynamic-form.d.ts.map +1 -1
- package/dist/core/form-field.d.ts.map +1 -1
- package/dist/core/form-section.d.ts +28 -2
- package/dist/core/form-section.d.ts.map +1 -1
- package/dist/index.js +312 -152
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1015 -298
- package/dist/index.mjs.map +1 -1
- package/dist/schema/types.d.ts +18 -0
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/xbrl-types.d.ts +20 -0
- package/dist/schema/xbrl-types.d.ts.map +1 -1
- package/dist/utils/formula-precondition-evaluator.d.ts +4 -1
- package/dist/utils/formula-precondition-evaluator.d.ts.map +1 -1
- package/dist/utils/formula-resolution-context.d.ts +29 -0
- package/dist/utils/formula-resolution-context.d.ts.map +1 -1
- package/dist/utils/formula-variable-resolver.d.ts +96 -4
- package/dist/utils/formula-variable-resolver.d.ts.map +1 -1
- package/dist/utils/type-input-mapping.d.ts +4 -4
- package/dist/utils/type-input-mapping.d.ts.map +1 -1
- package/dist/utils/xbrl-form-builder.d.ts.map +1 -1
- package/dist/utils/xbrl-validation.d.ts +9 -0
- package/dist/utils/xbrl-validation.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -989,7 +989,9 @@ class XBRLFormBuilder {
|
|
|
989
989
|
axisId: dimension.id,
|
|
990
990
|
axisLabel,
|
|
991
991
|
typedMemberId: dimension.typedMember.id,
|
|
992
|
-
memberLabel: axisLabel
|
|
992
|
+
memberLabel: axisLabel,
|
|
993
|
+
type: dimension.typedMember.type,
|
|
994
|
+
validation: dimension.typedMember.validation
|
|
993
995
|
});
|
|
994
996
|
}
|
|
995
997
|
return result.length > 0 ? result : null;
|
|
@@ -1181,6 +1183,8 @@ class XBRLFormBuilder {
|
|
|
1181
1183
|
axisId: dimension.id,
|
|
1182
1184
|
axisLabel: axisLabel2,
|
|
1183
1185
|
typedMemberId: dimension.typedMember.id,
|
|
1186
|
+
type: dimension.typedMember.type,
|
|
1187
|
+
validation: dimension.typedMember.validation,
|
|
1184
1188
|
dimensionKey: `${axisLabel2} | [Typed Input]`,
|
|
1185
1189
|
dimensionIdKey: `${dimension.id}|[typed]`
|
|
1186
1190
|
};
|
|
@@ -1207,7 +1211,9 @@ class XBRLFormBuilder {
|
|
|
1207
1211
|
axisId: dimensionInfo2.axisId,
|
|
1208
1212
|
axisLabel: dimensionInfo2.axisLabel,
|
|
1209
1213
|
typedMemberId: dimensionInfo2.typedMemberId,
|
|
1210
|
-
memberLabel: dimensionInfo2.axisLabel
|
|
1214
|
+
memberLabel: dimensionInfo2.axisLabel,
|
|
1215
|
+
type: dimensionInfo2.type,
|
|
1216
|
+
validation: dimensionInfo2.validation
|
|
1211
1217
|
}]
|
|
1212
1218
|
},
|
|
1213
1219
|
order: 0,
|
|
@@ -1237,7 +1243,9 @@ class XBRLFormBuilder {
|
|
|
1237
1243
|
axisId: dimensionInfo2.axisId,
|
|
1238
1244
|
axisLabel: dimensionInfo2.axisLabel,
|
|
1239
1245
|
typedMemberId: dimensionInfo2.typedMemberId,
|
|
1240
|
-
memberLabel: dimensionInfo2.axisLabel
|
|
1246
|
+
memberLabel: dimensionInfo2.axisLabel,
|
|
1247
|
+
type: dimensionInfo2.type,
|
|
1248
|
+
validation: dimensionInfo2.validation
|
|
1241
1249
|
}]
|
|
1242
1250
|
},
|
|
1243
1251
|
order: 0,
|
|
@@ -1267,7 +1275,9 @@ class XBRLFormBuilder {
|
|
|
1267
1275
|
axisId: dimensionInfo2.axisId,
|
|
1268
1276
|
axisLabel: dimensionInfo2.axisLabel,
|
|
1269
1277
|
typedMemberId: dimensionInfo2.typedMemberId,
|
|
1270
|
-
memberLabel: dimensionInfo2.axisLabel
|
|
1278
|
+
memberLabel: dimensionInfo2.axisLabel,
|
|
1279
|
+
type: dimensionInfo2.type,
|
|
1280
|
+
validation: dimensionInfo2.validation
|
|
1271
1281
|
}]
|
|
1272
1282
|
},
|
|
1273
1283
|
order: 0,
|
|
@@ -1297,7 +1307,9 @@ class XBRLFormBuilder {
|
|
|
1297
1307
|
axisId: dimensionInfo2.axisId,
|
|
1298
1308
|
axisLabel: dimensionInfo2.axisLabel,
|
|
1299
1309
|
typedMemberId: dimensionInfo2.typedMemberId,
|
|
1300
|
-
memberLabel: dimensionInfo2.axisLabel
|
|
1310
|
+
memberLabel: dimensionInfo2.axisLabel,
|
|
1311
|
+
type: dimensionInfo2.type,
|
|
1312
|
+
validation: dimensionInfo2.validation
|
|
1301
1313
|
}]
|
|
1302
1314
|
},
|
|
1303
1315
|
order: 0,
|
|
@@ -1442,6 +1454,8 @@ class XBRLFormBuilder {
|
|
|
1442
1454
|
axisLabel,
|
|
1443
1455
|
isTyped: true,
|
|
1444
1456
|
typedMemberId: dimension.typedMember.id,
|
|
1457
|
+
type: dimension.typedMember.type,
|
|
1458
|
+
validation: dimension.typedMember.validation,
|
|
1445
1459
|
members: [{
|
|
1446
1460
|
id: "[typed]",
|
|
1447
1461
|
label: `${axisLabel}`
|
|
@@ -1482,7 +1496,9 @@ class XBRLFormBuilder {
|
|
|
1482
1496
|
axisId: c2.axisId,
|
|
1483
1497
|
axisLabel: c2.axisLabel,
|
|
1484
1498
|
typedMemberId: c2.typedMemberId,
|
|
1485
|
-
memberLabel: c2.memberLabel
|
|
1499
|
+
memberLabel: c2.memberLabel,
|
|
1500
|
+
type: c2.type,
|
|
1501
|
+
validation: c2.validation
|
|
1486
1502
|
})),
|
|
1487
1503
|
hasTypedMembers: typedMembers.length > 0,
|
|
1488
1504
|
memberKey: columnTitle,
|
|
@@ -1537,7 +1553,9 @@ class XBRLFormBuilder {
|
|
|
1537
1553
|
memberId: member.id,
|
|
1538
1554
|
memberLabel: member.label,
|
|
1539
1555
|
isTyped: dimensionInfos[0].isTyped || false,
|
|
1540
|
-
typedMemberId: dimensionInfos[0].typedMemberId
|
|
1556
|
+
typedMemberId: dimensionInfos[0].typedMemberId,
|
|
1557
|
+
type: dimensionInfos[0].type,
|
|
1558
|
+
validation: dimensionInfos[0].validation
|
|
1541
1559
|
}]);
|
|
1542
1560
|
}
|
|
1543
1561
|
const [firstDimension, ...restDimensions] = dimensionInfos;
|
|
@@ -1551,7 +1569,9 @@ class XBRLFormBuilder {
|
|
|
1551
1569
|
memberId: member.id,
|
|
1552
1570
|
memberLabel: member.label,
|
|
1553
1571
|
isTyped: firstDimension.isTyped || false,
|
|
1554
|
-
typedMemberId: firstDimension.typedMemberId
|
|
1572
|
+
typedMemberId: firstDimension.typedMemberId,
|
|
1573
|
+
type: firstDimension.type,
|
|
1574
|
+
validation: firstDimension.validation
|
|
1555
1575
|
}]);
|
|
1556
1576
|
} else {
|
|
1557
1577
|
restCombinations.forEach((restCombination) => {
|
|
@@ -1562,7 +1582,9 @@ class XBRLFormBuilder {
|
|
|
1562
1582
|
memberId: member.id,
|
|
1563
1583
|
memberLabel: member.label,
|
|
1564
1584
|
isTyped: firstDimension.isTyped || false,
|
|
1565
|
-
typedMemberId: firstDimension.typedMemberId
|
|
1585
|
+
typedMemberId: firstDimension.typedMemberId,
|
|
1586
|
+
type: firstDimension.type,
|
|
1587
|
+
validation: firstDimension.validation
|
|
1566
1588
|
},
|
|
1567
1589
|
...restCombination
|
|
1568
1590
|
]);
|
|
@@ -1783,7 +1805,8 @@ const admin$1 = {
|
|
|
1783
1805
|
const validation$1 = {
|
|
1784
1806
|
summary: "Please fix the following errors before submitting:",
|
|
1785
1807
|
errorsFound: "errors found",
|
|
1786
|
-
unitRequired: "Unit is required. Please select a unit via the period icon."
|
|
1808
|
+
unitRequired: "Unit is required. Please select a unit via the period icon.",
|
|
1809
|
+
typedMemberRequired: "This field is required because data has been entered in this column."
|
|
1787
1810
|
};
|
|
1788
1811
|
const xbrlValidation$1 = {
|
|
1789
1812
|
patternMismatch: "Value does not match required pattern: {{pattern}}",
|
|
@@ -1989,7 +2012,8 @@ const admin = {
|
|
|
1989
2012
|
const validation = {
|
|
1990
2013
|
summary: "Corrigeer de volgende fouten voordat u indient:",
|
|
1991
2014
|
errorsFound: "fouten gevonden",
|
|
1992
|
-
unitRequired: "Eenheid is verplicht. Selecteer een eenheid via het periodeicoon."
|
|
2015
|
+
unitRequired: "Eenheid is verplicht. Selecteer een eenheid via het periodeicoon.",
|
|
2016
|
+
typedMemberRequired: "Dit veld is verplicht omdat er gegevens zijn ingevoerd in deze kolom."
|
|
1993
2017
|
};
|
|
1994
2018
|
const xbrlValidation = {
|
|
1995
2019
|
patternMismatch: "Waarde voldoet niet aan het vereiste patroon: {{pattern}}",
|
|
@@ -2766,13 +2790,24 @@ function indexConceptTree(concept, conceptByQName, conceptById, conceptIdByQName
|
|
|
2766
2790
|
);
|
|
2767
2791
|
children.forEach((child) => indexConceptTree(child, conceptByQName, conceptById, conceptIdByQName, conceptIdByQNameHasChildren, childrenIndex));
|
|
2768
2792
|
}
|
|
2769
|
-
function
|
|
2793
|
+
function collectMemberSubtreeIds(member) {
|
|
2794
|
+
const ids = [member.id];
|
|
2795
|
+
(member.children || []).forEach((child) => ids.push(...collectMemberSubtreeIds(child)));
|
|
2796
|
+
return ids;
|
|
2797
|
+
}
|
|
2798
|
+
function indexMemberTree(member, memberQNameToId, memberChildrenByQName, memberDescendantOrSelfIdsByQName, memberIdsWithParent) {
|
|
2770
2799
|
if (!memberQNameToId.has(member.conceptName))
|
|
2771
2800
|
memberQNameToId.set(member.conceptName, member.id);
|
|
2772
2801
|
if (!memberChildrenByQName.has(member.conceptName)) {
|
|
2773
2802
|
memberChildrenByQName.set(member.conceptName, (member.children || []).map((child) => child.id));
|
|
2774
2803
|
}
|
|
2775
|
-
(member.
|
|
2804
|
+
if (!memberDescendantOrSelfIdsByQName.has(member.conceptName)) {
|
|
2805
|
+
memberDescendantOrSelfIdsByQName.set(member.conceptName, collectMemberSubtreeIds(member));
|
|
2806
|
+
}
|
|
2807
|
+
(member.children || []).forEach((child) => {
|
|
2808
|
+
memberIdsWithParent.add(child.id);
|
|
2809
|
+
indexMemberTree(child, memberQNameToId, memberChildrenByQName, memberDescendantOrSelfIdsByQName, memberIdsWithParent);
|
|
2810
|
+
});
|
|
2776
2811
|
}
|
|
2777
2812
|
const PRESENTATION_TOTAL_GROUP_ACCESSORS = {
|
|
2778
2813
|
getId: (concept) => concept.id,
|
|
@@ -2802,16 +2837,35 @@ function buildFormulaResolutionContext(formData, columns, sections, hypercubeRol
|
|
|
2802
2837
|
if (section2.columns)
|
|
2803
2838
|
columnsByRole.set(section2.roleURI, section2.columns);
|
|
2804
2839
|
});
|
|
2840
|
+
const crossRoleChildrenByQName = /* @__PURE__ */ new Map();
|
|
2841
|
+
conceptIdByQNamePerRole.forEach((conceptIdByQName, roleURI) => {
|
|
2842
|
+
const childrenIndex = childrenByRole.get(roleURI);
|
|
2843
|
+
const totalGroupIndex = totalGroupChildrenByRole.get(roleURI);
|
|
2844
|
+
conceptIdByQName.forEach((conceptId, qname) => {
|
|
2845
|
+
var _a;
|
|
2846
|
+
const nestedChildren = (childrenIndex == null ? void 0 : childrenIndex.get(conceptId)) || [];
|
|
2847
|
+
const children = nestedChildren.length > 0 ? nestedChildren : (totalGroupIndex == null ? void 0 : totalGroupIndex.get(conceptId)) || [];
|
|
2848
|
+
if (children.length === 0)
|
|
2849
|
+
return;
|
|
2850
|
+
if (!crossRoleChildrenByQName.has(qname))
|
|
2851
|
+
crossRoleChildrenByQName.set(qname, /* @__PURE__ */ new Map());
|
|
2852
|
+
(_a = crossRoleChildrenByQName.get(qname)) == null ? void 0 : _a.set(roleURI, children);
|
|
2853
|
+
});
|
|
2854
|
+
});
|
|
2805
2855
|
const dimensionQNameToId = /* @__PURE__ */ new Map();
|
|
2806
2856
|
const memberQNameToId = /* @__PURE__ */ new Map();
|
|
2807
2857
|
const memberChildrenByQName = /* @__PURE__ */ new Map();
|
|
2858
|
+
const memberDescendantOrSelfIdsByQName = /* @__PURE__ */ new Map();
|
|
2859
|
+
const memberIdsWithParent = /* @__PURE__ */ new Set();
|
|
2808
2860
|
(hypercubeRoles || []).forEach((role) => {
|
|
2809
2861
|
role.items.forEach((item) => {
|
|
2810
2862
|
item.dimensions.forEach((dimension) => {
|
|
2811
2863
|
if (!dimensionQNameToId.has(dimension.conceptName)) {
|
|
2812
2864
|
dimensionQNameToId.set(dimension.conceptName, dimension.id);
|
|
2813
2865
|
}
|
|
2814
|
-
(dimension.members || []).forEach(
|
|
2866
|
+
(dimension.members || []).forEach(
|
|
2867
|
+
(member) => indexMemberTree(member, memberQNameToId, memberChildrenByQName, memberDescendantOrSelfIdsByQName, memberIdsWithParent)
|
|
2868
|
+
);
|
|
2815
2869
|
});
|
|
2816
2870
|
});
|
|
2817
2871
|
});
|
|
@@ -2825,9 +2879,12 @@ function buildFormulaResolutionContext(formData, columns, sections, hypercubeRol
|
|
|
2825
2879
|
conceptIdByQNamePerRole,
|
|
2826
2880
|
childrenByRole,
|
|
2827
2881
|
totalGroupChildrenByRole,
|
|
2882
|
+
crossRoleChildrenByQName,
|
|
2828
2883
|
dimensionQNameToId,
|
|
2829
2884
|
memberQNameToId,
|
|
2830
2885
|
memberChildrenByQName,
|
|
2886
|
+
memberDescendantOrSelfIdsByQName,
|
|
2887
|
+
memberIdsWithParent,
|
|
2831
2888
|
columnsByRole,
|
|
2832
2889
|
roleURIByConceptId,
|
|
2833
2890
|
allCandidates: collectAllCandidates(formData),
|
|
@@ -2951,6 +3008,21 @@ function resolveChildMemberAxisFilter(memberQName, axisId, universe, ctx, siblin
|
|
|
2951
3008
|
return [];
|
|
2952
3009
|
return universe.filter((candidate) => !directChildren.has(candidate.conceptId));
|
|
2953
3010
|
}
|
|
3011
|
+
function resolveDescendantOrSelfMemberAxisFilter(memberQName, axisId, universe, ctx) {
|
|
3012
|
+
const memberIds = ctx.memberDescendantOrSelfIdsByQName.get(memberQName) || [];
|
|
3013
|
+
if (memberIds.length === 0)
|
|
3014
|
+
return [];
|
|
3015
|
+
return universe.filter((candidate) => memberIds.some((id) => columnMatchesExplicitDimension(getColumn(ctx, candidate), axisId, id)));
|
|
3016
|
+
}
|
|
3017
|
+
function resolveCrossRoleChildren(anchorQName, ownLinkrole, ctx) {
|
|
3018
|
+
if (!anchorQName)
|
|
3019
|
+
return [];
|
|
3020
|
+
const candidatesByRole = ctx.crossRoleChildrenByQName.get(anchorQName);
|
|
3021
|
+
if (!candidatesByRole)
|
|
3022
|
+
return [];
|
|
3023
|
+
const otherRoles = Array.from(candidatesByRole.entries()).filter(([roleURI]) => roleURI !== ownLinkrole);
|
|
3024
|
+
return otherRoles.length === 1 ? otherRoles[0][1] : [];
|
|
3025
|
+
}
|
|
2954
3026
|
function matchFilter(filter2, universe, ctx, siblingFilters) {
|
|
2955
3027
|
var _a, _b;
|
|
2956
3028
|
switch (filter2.type) {
|
|
@@ -2983,6 +3055,9 @@ function matchFilter(filter2, universe, ctx, siblingFilters) {
|
|
|
2983
3055
|
} else {
|
|
2984
3056
|
const nestedChildren = childrenIndex.get(anchorConceptId) || [];
|
|
2985
3057
|
relatedConceptIds = nestedChildren.length > 0 ? nestedChildren : ((_b = ctx.totalGroupChildrenByRole.get(linkrole ?? "")) == null ? void 0 : _b.get(anchorConceptId)) || [];
|
|
3058
|
+
if (relatedConceptIds.length === 0) {
|
|
3059
|
+
relatedConceptIds = resolveCrossRoleChildren(anchorQName, linkrole, ctx);
|
|
3060
|
+
}
|
|
2986
3061
|
}
|
|
2987
3062
|
const relatedSet = new Set(relatedConceptIds);
|
|
2988
3063
|
return universe.filter((candidate) => relatedSet.has(candidate.conceptId));
|
|
@@ -2993,9 +3068,13 @@ function matchFilter(filter2, universe, ctx, siblingFilters) {
|
|
|
2993
3068
|
const axisId = dimensionQName ? ctx.dimensionQNameToId.get(dimensionQName) : void 0;
|
|
2994
3069
|
if (!axisId)
|
|
2995
3070
|
return [];
|
|
2996
|
-
|
|
3071
|
+
const memberAxis = filter2.attributes["member.axis"];
|
|
3072
|
+
if (memberQName && memberAxis === "child") {
|
|
2997
3073
|
return resolveChildMemberAxisFilter(memberQName, axisId, universe, ctx, siblingFilters);
|
|
2998
3074
|
}
|
|
3075
|
+
if (memberQName && memberAxis === "descendant-or-self") {
|
|
3076
|
+
return resolveDescendantOrSelfMemberAxisFilter(memberQName, axisId, universe, ctx);
|
|
3077
|
+
}
|
|
2999
3078
|
const memberId = memberQName ? ctx.memberQNameToId.get(memberQName) : void 0;
|
|
3000
3079
|
return universe.filter((candidate) => columnMatchesExplicitDimension(getColumn(ctx, candidate), axisId, memberId));
|
|
3001
3080
|
}
|
|
@@ -3015,8 +3094,17 @@ function matchFilter(filter2, universe, ctx, siblingFilters) {
|
|
|
3015
3094
|
return unionCandidates(filter2.children.map((child) => resolveFilter(child, universe, ctx, siblingFilters)));
|
|
3016
3095
|
case "ASPECT_COVER":
|
|
3017
3096
|
return universe;
|
|
3018
|
-
case "UNKNOWN":
|
|
3097
|
+
case "UNKNOWN": {
|
|
3098
|
+
const boundary = filter2.attributes["boundary"];
|
|
3099
|
+
if (boundary === "start" || boundary === "end") {
|
|
3100
|
+
const requiredRole = boundary === "start" ? "periodStartLabel" : "periodEndLabel";
|
|
3101
|
+
return universe.filter((candidate) => {
|
|
3102
|
+
var _a2;
|
|
3103
|
+
return ((_a2 = ctx.conceptById.get(candidate.conceptId)) == null ? void 0 : _a2.effectivePeriodRole) === requiredRole;
|
|
3104
|
+
});
|
|
3105
|
+
}
|
|
3019
3106
|
return universe;
|
|
3107
|
+
}
|
|
3020
3108
|
default:
|
|
3021
3109
|
return [];
|
|
3022
3110
|
}
|
|
@@ -3104,18 +3192,20 @@ function narrowToDirectChildOfDescendantAnchor(candidates, variable, ctx) {
|
|
|
3104
3192
|
return candidates;
|
|
3105
3193
|
return candidates.filter((candidate) => directChildren.has(candidate.conceptId));
|
|
3106
3194
|
}
|
|
3107
|
-
function
|
|
3108
|
-
|
|
3195
|
+
function resolveNarrowedCandidates(variable, ctx) {
|
|
3196
|
+
return narrowByPreferredOccurrence(
|
|
3109
3197
|
narrowToDirectChildOfDescendantAnchor(narrowToCurrentPeriod(resolveMatchedCandidates(variable, ctx), variable, ctx), variable, ctx),
|
|
3110
3198
|
ctx
|
|
3111
3199
|
);
|
|
3200
|
+
}
|
|
3201
|
+
function valuesFromCandidates(variable, matched, ctx) {
|
|
3112
3202
|
const values = matched.map((candidate) => {
|
|
3113
3203
|
var _a;
|
|
3114
3204
|
return resolveRawValue((_a = ctx.formData[candidate.conceptId]) == null ? void 0 : _a[candidate.columnId]);
|
|
3115
3205
|
}).filter((value) => value !== void 0);
|
|
3206
|
+
if (values.length === 0 && variable.fallbackValue === "()")
|
|
3207
|
+
return [];
|
|
3116
3208
|
if (variable.bindAsSequence) {
|
|
3117
|
-
if (values.length === 0 && variable.fallbackValue === "()")
|
|
3118
|
-
return [];
|
|
3119
3209
|
if (values.length === 0)
|
|
3120
3210
|
return void 0;
|
|
3121
3211
|
return values;
|
|
@@ -3129,6 +3219,186 @@ function resolveFormulaVariable(variable, ctx) {
|
|
|
3129
3219
|
}
|
|
3130
3220
|
return values[0];
|
|
3131
3221
|
}
|
|
3222
|
+
function uncoveredExplicitDimensionAxisIds(variable, ctx) {
|
|
3223
|
+
const axisIds = /* @__PURE__ */ new Set();
|
|
3224
|
+
for (const filter2 of variable.filters) {
|
|
3225
|
+
if (filter2.type !== "EXPLICIT_DIMENSION" || filter2.complement || filter2.cover)
|
|
3226
|
+
continue;
|
|
3227
|
+
if (filter2.attributes["member.qname"])
|
|
3228
|
+
continue;
|
|
3229
|
+
const axisId = ctx.dimensionQNameToId.get(filter2.attributes["dimension.qname"] ?? "");
|
|
3230
|
+
if (axisId)
|
|
3231
|
+
axisIds.add(axisId);
|
|
3232
|
+
}
|
|
3233
|
+
return axisIds;
|
|
3234
|
+
}
|
|
3235
|
+
function candidateMemberIdForAxis(ctx, candidate, axisId) {
|
|
3236
|
+
var _a;
|
|
3237
|
+
const column2 = getColumn(ctx, candidate);
|
|
3238
|
+
if (!(column2 == null ? void 0 : column2.dimensionData))
|
|
3239
|
+
return void 0;
|
|
3240
|
+
if (column2.dimensionData.axisId === axisId)
|
|
3241
|
+
return column2.dimensionData.memberId;
|
|
3242
|
+
return (_a = (column2.dimensionData.combinations || []).find((combo) => combo.axisId === axisId)) == null ? void 0 : _a.memberId;
|
|
3243
|
+
}
|
|
3244
|
+
function findUncoveredAxisAnchor(variables, axisId, candidatesByName, ctx) {
|
|
3245
|
+
for (const variable of variables) {
|
|
3246
|
+
if (variable.bindAsSequence)
|
|
3247
|
+
continue;
|
|
3248
|
+
if (!uncoveredExplicitDimensionAxisIds(variable, ctx).has(axisId))
|
|
3249
|
+
continue;
|
|
3250
|
+
const candidates = candidatesByName.get(variable.name) || [];
|
|
3251
|
+
const memberIds = new Set(
|
|
3252
|
+
candidates.map((candidate) => candidateMemberIdForAxis(ctx, candidate, axisId)).filter((id) => id !== void 0)
|
|
3253
|
+
);
|
|
3254
|
+
if (memberIds.size === 1)
|
|
3255
|
+
return { memberId: [...memberIds][0], variableName: variable.name, ambiguous: false };
|
|
3256
|
+
const rootMembers = [...memberIds].filter((id) => !ctx.memberIdsWithParent.has(id));
|
|
3257
|
+
if (rootMembers.length === 1)
|
|
3258
|
+
return { memberId: rootMembers[0], variableName: variable.name, ambiguous: true };
|
|
3259
|
+
}
|
|
3260
|
+
return void 0;
|
|
3261
|
+
}
|
|
3262
|
+
function resolveAssertionVariableBindings(variables, ctx, implicitFiltering) {
|
|
3263
|
+
const candidatesByName = /* @__PURE__ */ new Map();
|
|
3264
|
+
variables.forEach((variable) => candidatesByName.set(variable.name, resolveNarrowedCandidates(variable, ctx)));
|
|
3265
|
+
if (implicitFiltering) {
|
|
3266
|
+
const axisIds = /* @__PURE__ */ new Set();
|
|
3267
|
+
variables.forEach((variable) => uncoveredExplicitDimensionAxisIds(variable, ctx).forEach((axisId) => axisIds.add(axisId)));
|
|
3268
|
+
axisIds.forEach((axisId) => {
|
|
3269
|
+
const anchor = findUncoveredAxisAnchor(variables, axisId, candidatesByName, ctx);
|
|
3270
|
+
if (!anchor)
|
|
3271
|
+
return;
|
|
3272
|
+
variables.forEach((variable) => {
|
|
3273
|
+
const isNarrowableSequence = variable.bindAsSequence && uncoveredExplicitDimensionAxisIds(variable, ctx).has(axisId);
|
|
3274
|
+
const isAmbiguousAnchorItself = anchor.ambiguous && variable.name === anchor.variableName;
|
|
3275
|
+
if (!isNarrowableSequence && !isAmbiguousAnchorItself)
|
|
3276
|
+
return;
|
|
3277
|
+
const candidates = candidatesByName.get(variable.name) || [];
|
|
3278
|
+
candidatesByName.set(
|
|
3279
|
+
variable.name,
|
|
3280
|
+
candidates.filter((candidate) => candidateMemberIdForAxis(ctx, candidate, axisId) === anchor.memberId)
|
|
3281
|
+
);
|
|
3282
|
+
});
|
|
3283
|
+
});
|
|
3284
|
+
}
|
|
3285
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
3286
|
+
variables.forEach((variable) => bindings.set(variable.name, valuesFromCandidates(variable, candidatesByName.get(variable.name) || [], ctx)));
|
|
3287
|
+
return bindings;
|
|
3288
|
+
}
|
|
3289
|
+
function filterSignature(filter2) {
|
|
3290
|
+
const attrs = Object.entries(filter2.attributes).filter(([key]) => key !== "id").sort(([a2], [b2]) => a2.localeCompare(b2));
|
|
3291
|
+
return JSON.stringify({ type: filter2.type, complement: filter2.complement, cover: filter2.cover, attrs });
|
|
3292
|
+
}
|
|
3293
|
+
function detectSumOfMembersOnAbstractShape(assertion) {
|
|
3294
|
+
if (assertion.type !== "VALUE_ASSERTION" || assertion.variables.length !== 2)
|
|
3295
|
+
return void 0;
|
|
3296
|
+
const scalarVariable = assertion.variables.find((v) => !v.bindAsSequence);
|
|
3297
|
+
const sequenceVariable = assertion.variables.find((v) => v.bindAsSequence);
|
|
3298
|
+
if (!scalarVariable || !sequenceVariable)
|
|
3299
|
+
return void 0;
|
|
3300
|
+
const isDescendantRelation = (f2) => f2.type === "CONCEPT_RELATION" && f2.attributes["axis"] === "descendant" && !f2.complement && !f2.cover;
|
|
3301
|
+
const scalarCR = scalarVariable.filters.find(isDescendantRelation);
|
|
3302
|
+
const seqCR = sequenceVariable.filters.find(isDescendantRelation);
|
|
3303
|
+
if (!scalarCR || !seqCR)
|
|
3304
|
+
return void 0;
|
|
3305
|
+
const anchorQName = scalarCR.attributes["qname"];
|
|
3306
|
+
const linkrole = scalarCR.attributes["linkrole"];
|
|
3307
|
+
if (!anchorQName || !linkrole)
|
|
3308
|
+
return void 0;
|
|
3309
|
+
if (seqCR.attributes["qname"] !== anchorQName || seqCR.attributes["linkrole"] !== linkrole)
|
|
3310
|
+
return void 0;
|
|
3311
|
+
const scalarED = scalarVariable.filters.find(
|
|
3312
|
+
(f2) => f2.type === "EXPLICIT_DIMENSION" && !f2.complement && f2.cover && !("member.axis" in f2.attributes)
|
|
3313
|
+
);
|
|
3314
|
+
const seqED = sequenceVariable.filters.find(
|
|
3315
|
+
(f2) => f2.type === "EXPLICIT_DIMENSION" && !f2.complement && f2.cover && f2.attributes["member.axis"] === "child"
|
|
3316
|
+
);
|
|
3317
|
+
if (!scalarED || !seqED)
|
|
3318
|
+
return void 0;
|
|
3319
|
+
const dimensionQName = scalarED.attributes["dimension.qname"];
|
|
3320
|
+
const memberQName = scalarED.attributes["member.qname"];
|
|
3321
|
+
if (!dimensionQName || !memberQName)
|
|
3322
|
+
return void 0;
|
|
3323
|
+
if (seqED.attributes["dimension.qname"] !== dimensionQName || seqED.attributes["member.qname"] !== memberQName)
|
|
3324
|
+
return void 0;
|
|
3325
|
+
const scalarOther = scalarVariable.filters.filter((f2) => f2 !== scalarCR && f2 !== scalarED).map(filterSignature).sort();
|
|
3326
|
+
const seqOther = sequenceVariable.filters.filter((f2) => f2 !== seqCR && f2 !== seqED).map(filterSignature).sort();
|
|
3327
|
+
if (scalarOther.length !== seqOther.length || scalarOther.some((sig, i2) => sig !== seqOther[i2]))
|
|
3328
|
+
return void 0;
|
|
3329
|
+
return { scalarVariable, sequenceVariable, anchorQName, linkrole, dimensionQName, memberQName };
|
|
3330
|
+
}
|
|
3331
|
+
function resolveSumOfMembersOnAbstractSatisfaction(shape, ctx) {
|
|
3332
|
+
var _a;
|
|
3333
|
+
const { scalarVariable, sequenceVariable, anchorQName, linkrole, dimensionQName, memberQName } = shape;
|
|
3334
|
+
const anchorConceptId = (_a = ctx.conceptIdByQNamePerRole.get(linkrole)) == null ? void 0 : _a.get(anchorQName);
|
|
3335
|
+
const childrenIndex = ctx.childrenByRole.get(linkrole);
|
|
3336
|
+
if (!anchorConceptId || !childrenIndex)
|
|
3337
|
+
return "not-applicable";
|
|
3338
|
+
const descendantConceptIds = collectDescendantConceptIds(childrenIndex, anchorConceptId);
|
|
3339
|
+
if (descendantConceptIds.length === 0)
|
|
3340
|
+
return "not-applicable";
|
|
3341
|
+
const axisId = ctx.dimensionQNameToId.get(dimensionQName);
|
|
3342
|
+
const rootMemberId = ctx.memberQNameToId.get(memberQName);
|
|
3343
|
+
const childMemberIds = ctx.memberChildrenByQName.get(memberQName) || [];
|
|
3344
|
+
if (!axisId || !rootMemberId || childMemberIds.length === 0)
|
|
3345
|
+
return "not-applicable";
|
|
3346
|
+
const sharedFilters = scalarVariable.filters.filter(
|
|
3347
|
+
(f2) => !(f2.type === "CONCEPT_RELATION" && f2.attributes["axis"] === "descendant") && !(f2.type === "EXPLICIT_DIMENSION" && f2.attributes["dimension.qname"] === dimensionQName)
|
|
3348
|
+
);
|
|
3349
|
+
const sharedPool = sharedFilters.length > 0 ? intersectCandidates(sharedFilters.map((f2) => resolveFilter(f2, ctx.allCandidates, ctx, sharedFilters))) : ctx.allCandidates;
|
|
3350
|
+
const descendantSet = new Set(descendantConceptIds);
|
|
3351
|
+
const poolByConceptId = /* @__PURE__ */ new Map();
|
|
3352
|
+
sharedPool.forEach((candidate) => {
|
|
3353
|
+
if (!descendantSet.has(candidate.conceptId))
|
|
3354
|
+
return;
|
|
3355
|
+
const list = poolByConceptId.get(candidate.conceptId);
|
|
3356
|
+
if (list)
|
|
3357
|
+
list.push(candidate);
|
|
3358
|
+
else
|
|
3359
|
+
poolByConceptId.set(candidate.conceptId, [candidate]);
|
|
3360
|
+
});
|
|
3361
|
+
const hasGenuineChildMemberData = Array.from(poolByConceptId.values()).some((candidates) => candidates.some((candidate) => childMemberIds.some((id) => columnMatchesExplicitDimension(getColumn(ctx, candidate), axisId, id))));
|
|
3362
|
+
if (!hasGenuineChildMemberData)
|
|
3363
|
+
return "not-applicable";
|
|
3364
|
+
let comparedAny = false;
|
|
3365
|
+
for (const conceptId of descendantConceptIds) {
|
|
3366
|
+
const candidates = poolByConceptId.get(conceptId);
|
|
3367
|
+
if (!candidates || candidates.length === 0)
|
|
3368
|
+
continue;
|
|
3369
|
+
const scalarCandidates = narrowToCurrentPeriod(
|
|
3370
|
+
candidates.filter((candidate) => columnMatchesExplicitDimension(getColumn(ctx, candidate), axisId, rootMemberId)),
|
|
3371
|
+
scalarVariable,
|
|
3372
|
+
ctx
|
|
3373
|
+
);
|
|
3374
|
+
const childCandidates = narrowToCurrentPeriod(
|
|
3375
|
+
candidates.filter((candidate) => childMemberIds.some((id) => columnMatchesExplicitDimension(getColumn(ctx, candidate), axisId, id))),
|
|
3376
|
+
sequenceVariable,
|
|
3377
|
+
ctx
|
|
3378
|
+
);
|
|
3379
|
+
const scalarValues = scalarCandidates.map((candidate) => {
|
|
3380
|
+
var _a2;
|
|
3381
|
+
return resolveRawValue((_a2 = ctx.formData[candidate.conceptId]) == null ? void 0 : _a2[candidate.columnId]);
|
|
3382
|
+
}).filter((value) => typeof value === "number");
|
|
3383
|
+
const childValues = childCandidates.map((candidate) => {
|
|
3384
|
+
var _a2;
|
|
3385
|
+
return resolveRawValue((_a2 = ctx.formData[candidate.conceptId]) == null ? void 0 : _a2[candidate.columnId]);
|
|
3386
|
+
}).filter((value) => typeof value === "number");
|
|
3387
|
+
if (scalarValues.length === 0 || childValues.length === 0)
|
|
3388
|
+
continue;
|
|
3389
|
+
if (scalarValues.length > 1) {
|
|
3390
|
+
console.warn(
|
|
3391
|
+
`[formula-variable-resolver] SumOfMembersOnAbstract concept "${conceptId}" matched ${scalarValues.length} aggregate-member facts; using the first by column order.`
|
|
3392
|
+
);
|
|
3393
|
+
}
|
|
3394
|
+
const scalarValue = scalarValues[0];
|
|
3395
|
+
const childrenSum = childValues.reduce((sum, value) => sum + value, 0);
|
|
3396
|
+
comparedAny = true;
|
|
3397
|
+
if (Math.abs(scalarValue - childrenSum) >= CALCULATION_TOLERANCE)
|
|
3398
|
+
return false;
|
|
3399
|
+
}
|
|
3400
|
+
return comparedAny ? true : "unresolvable";
|
|
3401
|
+
}
|
|
3132
3402
|
function resolveFormulaVariableMatchCount(variable, ctx) {
|
|
3133
3403
|
return resolveMatchedCandidates(variable, ctx).length;
|
|
3134
3404
|
}
|
|
@@ -3171,11 +3441,7 @@ function resolveVariableColumnsForConcept(variable, targetConceptId, ctx) {
|
|
|
3171
3441
|
return resolved.map((candidate) => candidate.columnId);
|
|
3172
3442
|
}
|
|
3173
3443
|
function buildAssertionBindings(assertion, ctx) {
|
|
3174
|
-
|
|
3175
|
-
for (const variable of assertion.variables) {
|
|
3176
|
-
bindings.set(variable.name, resolveFormulaVariable(variable, ctx));
|
|
3177
|
-
}
|
|
3178
|
-
return bindings;
|
|
3444
|
+
return resolveAssertionVariableBindings(assertion.variables, ctx, assertion.implicitFiltering);
|
|
3179
3445
|
}
|
|
3180
3446
|
function buildAssertionFallbackUsage(assertion, ctx) {
|
|
3181
3447
|
var _a;
|
|
@@ -3268,7 +3534,10 @@ class FormulaValidationService {
|
|
|
3268
3534
|
_evaluateAssertion(assertion, role, ctx, language) {
|
|
3269
3535
|
try {
|
|
3270
3536
|
const bindings = buildAssertionBindings(assertion, ctx);
|
|
3271
|
-
|
|
3537
|
+
const sumOfMembersShape = detectSumOfMembersOnAbstractShape(assertion);
|
|
3538
|
+
const sumOfMembersResult = sumOfMembersShape ? resolveSumOfMembersOnAbstractSatisfaction(sumOfMembersShape, ctx) : void 0;
|
|
3539
|
+
const usesSumOfMembersPath = sumOfMembersResult !== void 0 && sumOfMembersResult !== "not-applicable";
|
|
3540
|
+
if (assertion.type === "VALUE_ASSERTION" && !usesSumOfMembersPath) {
|
|
3272
3541
|
const bindingCheck = checkAllVariablesBound(assertion, bindings);
|
|
3273
3542
|
if (!bindingCheck.satisfied) {
|
|
3274
3543
|
return this._skippedResult(assertion, role, bindingCheck.skipReason ?? "unresolvable");
|
|
@@ -3284,6 +3553,12 @@ class FormulaValidationService {
|
|
|
3284
3553
|
const satisfied2 = evaluateExistenceTest(assertion.test, matchedFactCount);
|
|
3285
3554
|
return this._evaluatedResult(assertion, role, ctx, satisfied2, language, matchedFactCount);
|
|
3286
3555
|
}
|
|
3556
|
+
if (usesSumOfMembersPath) {
|
|
3557
|
+
if (sumOfMembersResult === "unresolvable") {
|
|
3558
|
+
return this._skippedResult(assertion, role, "unresolvable");
|
|
3559
|
+
}
|
|
3560
|
+
return this._evaluatedResult(assertion, role, ctx, sumOfMembersResult, language);
|
|
3561
|
+
}
|
|
3287
3562
|
const satisfied = evaluateFormulaTest(assertion.test, bindings, fallbackUsage);
|
|
3288
3563
|
return this._evaluatedResult(assertion, role, ctx, satisfied, language);
|
|
3289
3564
|
} catch (error2) {
|
|
@@ -3612,204 +3887,6 @@ class FactMatcher {
|
|
|
3612
3887
|
console.log(" Match Result:", match ? match.value : "No match");
|
|
3613
3888
|
}
|
|
3614
3889
|
}
|
|
3615
|
-
class XBRLValidator {
|
|
3616
|
-
/**
|
|
3617
|
-
* Validates a value against XBRL datatype validation rules
|
|
3618
|
-
* Only validates when value is not blank
|
|
3619
|
-
*
|
|
3620
|
-
* @param conceptType - The XBRL concept type (e.g., "nl-types:houseNumberNLItemType")
|
|
3621
|
-
* @param value - The input value to validate
|
|
3622
|
-
* @param datatypes - Array of XBRL datatypes from taxonomy
|
|
3623
|
-
* @returns Validation result with errors if any
|
|
3624
|
-
*/
|
|
3625
|
-
static validateConceptValue(conceptType, value, datatypes) {
|
|
3626
|
-
const errors = [];
|
|
3627
|
-
if (value === null || value === void 0 || value === "") {
|
|
3628
|
-
return { valid: true, errors: [] };
|
|
3629
|
-
}
|
|
3630
|
-
if (!conceptType || !datatypes || datatypes.length === 0) {
|
|
3631
|
-
return { valid: true, errors: [] };
|
|
3632
|
-
}
|
|
3633
|
-
const datatype = this.findDatatype(conceptType, datatypes);
|
|
3634
|
-
if (!datatype || !datatype.validation) {
|
|
3635
|
-
return { valid: true, errors: [] };
|
|
3636
|
-
}
|
|
3637
|
-
const validation2 = datatype.validation;
|
|
3638
|
-
const stringValue = String(value);
|
|
3639
|
-
if (validation2.pattern) {
|
|
3640
|
-
const patternError = this.validatePattern(stringValue, validation2.pattern);
|
|
3641
|
-
if (patternError) {
|
|
3642
|
-
errors.push(patternError);
|
|
3643
|
-
}
|
|
3644
|
-
}
|
|
3645
|
-
if (validation2.length !== void 0) {
|
|
3646
|
-
const lengthError = this.validateExactLength(stringValue, validation2.length);
|
|
3647
|
-
if (lengthError) {
|
|
3648
|
-
errors.push(lengthError);
|
|
3649
|
-
}
|
|
3650
|
-
}
|
|
3651
|
-
if (validation2.minLength !== void 0) {
|
|
3652
|
-
const minLengthError = this.validateMinLength(stringValue, validation2.minLength);
|
|
3653
|
-
if (minLengthError) {
|
|
3654
|
-
errors.push(minLengthError);
|
|
3655
|
-
}
|
|
3656
|
-
}
|
|
3657
|
-
if (validation2.maxLength !== void 0) {
|
|
3658
|
-
const maxLengthError = this.validateMaxLength(stringValue, validation2.maxLength);
|
|
3659
|
-
if (maxLengthError) {
|
|
3660
|
-
errors.push(maxLengthError);
|
|
3661
|
-
}
|
|
3662
|
-
}
|
|
3663
|
-
if (validation2.totalDigits !== void 0) {
|
|
3664
|
-
const totalDigitsError = this.validateTotalDigits(stringValue, validation2.totalDigits);
|
|
3665
|
-
if (totalDigitsError) {
|
|
3666
|
-
errors.push(totalDigitsError);
|
|
3667
|
-
}
|
|
3668
|
-
}
|
|
3669
|
-
return {
|
|
3670
|
-
valid: errors.length === 0,
|
|
3671
|
-
errors
|
|
3672
|
-
};
|
|
3673
|
-
}
|
|
3674
|
-
/**
|
|
3675
|
-
* Finds the datatype definition for a given concept type
|
|
3676
|
-
* Handles both exact matches and base type chain resolution
|
|
3677
|
-
*/
|
|
3678
|
-
static findDatatype(conceptType, datatypes) {
|
|
3679
|
-
let datatype = datatypes.find((dt) => dt.type === conceptType);
|
|
3680
|
-
if (datatype) {
|
|
3681
|
-
return datatype;
|
|
3682
|
-
}
|
|
3683
|
-
const visited = /* @__PURE__ */ new Set();
|
|
3684
|
-
let currentType = conceptType;
|
|
3685
|
-
while (currentType && !visited.has(currentType)) {
|
|
3686
|
-
visited.add(currentType);
|
|
3687
|
-
datatype = datatypes.find((dt) => dt.type === currentType);
|
|
3688
|
-
if (datatype) {
|
|
3689
|
-
if (datatype.validation) {
|
|
3690
|
-
return datatype;
|
|
3691
|
-
}
|
|
3692
|
-
currentType = datatype.basetype;
|
|
3693
|
-
} else {
|
|
3694
|
-
break;
|
|
3695
|
-
}
|
|
3696
|
-
}
|
|
3697
|
-
return void 0;
|
|
3698
|
-
}
|
|
3699
|
-
/**
|
|
3700
|
-
* Validates that value matches the regex pattern
|
|
3701
|
-
*/
|
|
3702
|
-
static validatePattern(value, pattern) {
|
|
3703
|
-
try {
|
|
3704
|
-
const regex = new RegExp(`^${pattern}$`);
|
|
3705
|
-
if (!regex.test(value)) {
|
|
3706
|
-
return {
|
|
3707
|
-
type: "pattern",
|
|
3708
|
-
message: I18n.t("xbrlValidation.patternMismatch", { pattern }),
|
|
3709
|
-
expectedValue: pattern,
|
|
3710
|
-
actualValue: value
|
|
3711
|
-
};
|
|
3712
|
-
}
|
|
3713
|
-
} catch (error2) {
|
|
3714
|
-
console.error(`Invalid regex pattern: ${pattern}`, error2);
|
|
3715
|
-
return {
|
|
3716
|
-
type: "pattern",
|
|
3717
|
-
message: I18n.t("xbrlValidation.invalidPattern", { pattern }),
|
|
3718
|
-
expectedValue: pattern,
|
|
3719
|
-
actualValue: value
|
|
3720
|
-
};
|
|
3721
|
-
}
|
|
3722
|
-
return null;
|
|
3723
|
-
}
|
|
3724
|
-
/**
|
|
3725
|
-
* Validates that value has exact length
|
|
3726
|
-
*/
|
|
3727
|
-
static validateExactLength(value, requiredLength) {
|
|
3728
|
-
if (value.length !== requiredLength) {
|
|
3729
|
-
return {
|
|
3730
|
-
type: "length",
|
|
3731
|
-
message: I18n.t("xbrlValidation.exactLength", { required: requiredLength, current: value.length }),
|
|
3732
|
-
expectedValue: requiredLength,
|
|
3733
|
-
actualValue: value.length
|
|
3734
|
-
};
|
|
3735
|
-
}
|
|
3736
|
-
return null;
|
|
3737
|
-
}
|
|
3738
|
-
/**
|
|
3739
|
-
* Validates minimum length
|
|
3740
|
-
*/
|
|
3741
|
-
static validateMinLength(value, minLength) {
|
|
3742
|
-
if (value.length < minLength) {
|
|
3743
|
-
return {
|
|
3744
|
-
type: "minLength",
|
|
3745
|
-
message: I18n.t("xbrlValidation.minLength", { required: minLength, current: value.length }),
|
|
3746
|
-
expectedValue: minLength,
|
|
3747
|
-
actualValue: value.length
|
|
3748
|
-
};
|
|
3749
|
-
}
|
|
3750
|
-
return null;
|
|
3751
|
-
}
|
|
3752
|
-
/**
|
|
3753
|
-
* Validates maximum length
|
|
3754
|
-
*/
|
|
3755
|
-
static validateMaxLength(value, maxLength) {
|
|
3756
|
-
if (value.length > maxLength) {
|
|
3757
|
-
return {
|
|
3758
|
-
type: "maxLength",
|
|
3759
|
-
message: I18n.t("xbrlValidation.maxLength", { required: maxLength, current: value.length }),
|
|
3760
|
-
expectedValue: maxLength,
|
|
3761
|
-
actualValue: value.length
|
|
3762
|
-
};
|
|
3763
|
-
}
|
|
3764
|
-
return null;
|
|
3765
|
-
}
|
|
3766
|
-
/**
|
|
3767
|
-
* Validates total digits (for numeric values)
|
|
3768
|
-
*/
|
|
3769
|
-
static validateTotalDigits(value, totalDigits) {
|
|
3770
|
-
const digitsOnly = value.replace(/[^\d]/g, "");
|
|
3771
|
-
if (digitsOnly.length > totalDigits) {
|
|
3772
|
-
return {
|
|
3773
|
-
type: "totalDigits",
|
|
3774
|
-
message: I18n.t("xbrlValidation.totalDigits", { required: totalDigits, current: digitsOnly.length }),
|
|
3775
|
-
expectedValue: totalDigits,
|
|
3776
|
-
actualValue: digitsOnly.length
|
|
3777
|
-
};
|
|
3778
|
-
}
|
|
3779
|
-
return null;
|
|
3780
|
-
}
|
|
3781
|
-
/**
|
|
3782
|
-
* Gets a human-readable summary of validation rules for a concept type
|
|
3783
|
-
* Useful for displaying validation hints to users
|
|
3784
|
-
*/
|
|
3785
|
-
static getValidationRulesSummary(conceptType, datatypes) {
|
|
3786
|
-
if (!conceptType || !datatypes) {
|
|
3787
|
-
return [];
|
|
3788
|
-
}
|
|
3789
|
-
const datatype = this.findDatatype(conceptType, datatypes);
|
|
3790
|
-
if (!datatype || !datatype.validation) {
|
|
3791
|
-
return [];
|
|
3792
|
-
}
|
|
3793
|
-
const validation2 = datatype.validation;
|
|
3794
|
-
const rules = [];
|
|
3795
|
-
if (validation2.pattern) {
|
|
3796
|
-
rules.push(I18n.t("xbrlValidation.ruleSummaryPattern", { pattern: validation2.pattern }));
|
|
3797
|
-
}
|
|
3798
|
-
if (validation2.length !== void 0) {
|
|
3799
|
-
rules.push(I18n.t("xbrlValidation.ruleSummaryExactLength", { required: validation2.length }));
|
|
3800
|
-
}
|
|
3801
|
-
if (validation2.minLength !== void 0) {
|
|
3802
|
-
rules.push(I18n.t("xbrlValidation.ruleSummaryMinLength", { required: validation2.minLength }));
|
|
3803
|
-
}
|
|
3804
|
-
if (validation2.maxLength !== void 0) {
|
|
3805
|
-
rules.push(I18n.t("xbrlValidation.ruleSummaryMaxLength", { required: validation2.maxLength }));
|
|
3806
|
-
}
|
|
3807
|
-
if (validation2.totalDigits !== void 0) {
|
|
3808
|
-
rules.push(I18n.t("xbrlValidation.ruleSummaryTotalDigits", { required: validation2.totalDigits }));
|
|
3809
|
-
}
|
|
3810
|
-
return rules;
|
|
3811
|
-
}
|
|
3812
|
-
}
|
|
3813
3890
|
const TYPE_INPUT_MAP = {
|
|
3814
3891
|
// ==========================================
|
|
3815
3892
|
// Dutch XBRL Types (nl-types namespace)
|
|
@@ -3846,14 +3923,21 @@ const TYPE_INPUT_MAP = {
|
|
|
3846
3923
|
htmlInputType: "text",
|
|
3847
3924
|
placeholder: I18n.t("field.enterText")
|
|
3848
3925
|
},
|
|
3849
|
-
// Date types
|
|
3926
|
+
// Date types â bounded to a sane 1900-2100 range. Browsers don't stop you from *typing* a
|
|
3927
|
+
// year outside this (see MDN: min/max restrict the calendar picker and validity state, not
|
|
3928
|
+
// keystrokes), but it does keep the calendar picker itself sane and correctly flags an
|
|
3929
|
+
// out-of-range typed value as :invalid without us clearing/rejecting anything ourselves.
|
|
3850
3930
|
"xbrli:dateItemType": {
|
|
3851
3931
|
fieldType: "date",
|
|
3852
|
-
htmlInputType: "date"
|
|
3932
|
+
htmlInputType: "date",
|
|
3933
|
+
min: "1900-01-01",
|
|
3934
|
+
max: "2100-12-31"
|
|
3853
3935
|
},
|
|
3854
3936
|
"xbrli:dateTimeItemType": {
|
|
3855
3937
|
fieldType: "datetime",
|
|
3856
|
-
htmlInputType: "datetime-local"
|
|
3938
|
+
htmlInputType: "datetime-local",
|
|
3939
|
+
min: "1900-01-01T00:00",
|
|
3940
|
+
max: "2100-12-31T23:59"
|
|
3857
3941
|
},
|
|
3858
3942
|
// Numeric types - integers
|
|
3859
3943
|
"xbrli:integerItemType": {
|
|
@@ -4013,12 +4097,16 @@ function getInputTypeForConceptType(conceptType, datatypes) {
|
|
|
4013
4097
|
if (lowerType.includes("time")) {
|
|
4014
4098
|
return {
|
|
4015
4099
|
fieldType: "datetime",
|
|
4016
|
-
htmlInputType: "datetime-local"
|
|
4100
|
+
htmlInputType: "datetime-local",
|
|
4101
|
+
min: "1900-01-01T00:00",
|
|
4102
|
+
max: "2100-12-31T23:59"
|
|
4017
4103
|
};
|
|
4018
4104
|
}
|
|
4019
4105
|
return {
|
|
4020
4106
|
fieldType: "date",
|
|
4021
|
-
htmlInputType: "date"
|
|
4107
|
+
htmlInputType: "date",
|
|
4108
|
+
min: "1900-01-01",
|
|
4109
|
+
max: "2100-12-31"
|
|
4022
4110
|
};
|
|
4023
4111
|
}
|
|
4024
4112
|
if (lowerType.includes("boolean") || lowerType.includes("bool")) {
|
|
@@ -4151,12 +4239,16 @@ function determineInputTypeFromBaseChain(baseTypeChain, datatypes) {
|
|
|
4151
4239
|
if (chainString.includes("datetime") || chainString.includes("time")) {
|
|
4152
4240
|
return {
|
|
4153
4241
|
fieldType: "datetime",
|
|
4154
|
-
htmlInputType: "datetime-local"
|
|
4242
|
+
htmlInputType: "datetime-local",
|
|
4243
|
+
min: "1900-01-01T00:00",
|
|
4244
|
+
max: "2100-12-31T23:59"
|
|
4155
4245
|
};
|
|
4156
4246
|
}
|
|
4157
4247
|
return {
|
|
4158
4248
|
fieldType: "date",
|
|
4159
|
-
htmlInputType: "date"
|
|
4249
|
+
htmlInputType: "date",
|
|
4250
|
+
min: "1900-01-01",
|
|
4251
|
+
max: "2100-12-31"
|
|
4160
4252
|
};
|
|
4161
4253
|
}
|
|
4162
4254
|
if (chainLower.some((type) => type.includes("boolean") || type.includes("bool"))) {
|
|
@@ -4187,48 +4279,262 @@ function determineInputTypeFromBaseChain(baseTypeChain, datatypes) {
|
|
|
4187
4279
|
placeholder: I18n.t("field.enterPercentage")
|
|
4188
4280
|
};
|
|
4189
4281
|
}
|
|
4190
|
-
if (chainLower.some((type) => type.includes("decimal") || type === "decimal")) {
|
|
4191
|
-
if (chainString.includes("monetary") || chainString.includes("currency")) {
|
|
4192
|
-
if (chainString.includes("nodecimals") || chainString.includes("no-decimals") || chainString.includes("nodecimals20")) {
|
|
4193
|
-
return {
|
|
4194
|
-
fieldType: "number",
|
|
4195
|
-
htmlInputType: "text",
|
|
4196
|
-
inputMode: "decimal",
|
|
4197
|
-
allowDecimals: false,
|
|
4198
|
-
step: 1,
|
|
4199
|
-
placeholder: I18n.t("field.enterAmountNoDecimals")
|
|
4200
|
-
};
|
|
4201
|
-
}
|
|
4282
|
+
if (chainLower.some((type) => type.includes("decimal") || type === "decimal")) {
|
|
4283
|
+
if (chainString.includes("monetary") || chainString.includes("currency")) {
|
|
4284
|
+
if (chainString.includes("nodecimals") || chainString.includes("no-decimals") || chainString.includes("nodecimals20")) {
|
|
4285
|
+
return {
|
|
4286
|
+
fieldType: "number",
|
|
4287
|
+
htmlInputType: "text",
|
|
4288
|
+
inputMode: "decimal",
|
|
4289
|
+
allowDecimals: false,
|
|
4290
|
+
step: 1,
|
|
4291
|
+
placeholder: I18n.t("field.enterAmountNoDecimals")
|
|
4292
|
+
};
|
|
4293
|
+
}
|
|
4294
|
+
return {
|
|
4295
|
+
fieldType: "currency",
|
|
4296
|
+
htmlInputType: "text",
|
|
4297
|
+
inputMode: "decimal",
|
|
4298
|
+
allowDecimals: true,
|
|
4299
|
+
step: 0.01,
|
|
4300
|
+
placeholder: I18n.t("field.enterCurrency")
|
|
4301
|
+
};
|
|
4302
|
+
}
|
|
4303
|
+
return {
|
|
4304
|
+
fieldType: "decimal",
|
|
4305
|
+
htmlInputType: "text",
|
|
4306
|
+
inputMode: "decimal",
|
|
4307
|
+
allowDecimals: true,
|
|
4308
|
+
step: "any",
|
|
4309
|
+
placeholder: I18n.t("field.enterDecimalValue")
|
|
4310
|
+
};
|
|
4311
|
+
}
|
|
4312
|
+
if (baseTypeChain.some((type) => type === "xbrli:stringItemType") || chainLower.some((type) => type === "string" || type.includes("stringitem"))) {
|
|
4313
|
+
return {
|
|
4314
|
+
fieldType: "text",
|
|
4315
|
+
htmlInputType: "text",
|
|
4316
|
+
placeholder: I18n.t("field.enterText")
|
|
4317
|
+
};
|
|
4318
|
+
}
|
|
4319
|
+
return {
|
|
4320
|
+
fieldType: "text",
|
|
4321
|
+
htmlInputType: "text",
|
|
4322
|
+
placeholder: I18n.t("field.enterValue")
|
|
4323
|
+
};
|
|
4324
|
+
}
|
|
4325
|
+
class XBRLValidator {
|
|
4326
|
+
/**
|
|
4327
|
+
* Validates a value against XBRL datatype validation rules
|
|
4328
|
+
* Only validates when value is not blank
|
|
4329
|
+
*
|
|
4330
|
+
* @param conceptType - The XBRL concept type (e.g., "nl-types:houseNumberNLItemType")
|
|
4331
|
+
* @param value - The input value to validate
|
|
4332
|
+
* @param datatypes - Array of XBRL datatypes from taxonomy
|
|
4333
|
+
* @returns Validation result with errors if any
|
|
4334
|
+
*/
|
|
4335
|
+
static validateConceptValue(conceptType, value, datatypes) {
|
|
4336
|
+
if (value === null || value === void 0 || value === "") {
|
|
4337
|
+
return { valid: true, errors: [] };
|
|
4338
|
+
}
|
|
4339
|
+
if (!conceptType || !datatypes || datatypes.length === 0) {
|
|
4340
|
+
return { valid: true, errors: [] };
|
|
4341
|
+
}
|
|
4342
|
+
const datatype = this.findDatatype(conceptType, datatypes);
|
|
4343
|
+
if (!datatype || !datatype.validation) {
|
|
4344
|
+
return { valid: true, errors: [] };
|
|
4345
|
+
}
|
|
4346
|
+
return this.validateAgainstRules(value, datatype.validation);
|
|
4347
|
+
}
|
|
4348
|
+
/**
|
|
4349
|
+
* Validates a value against an already-resolved validation rule set, skipping the
|
|
4350
|
+
* type-name â datatypes.json lookup `validateConceptValue` does. Use this when the caller
|
|
4351
|
+
* already has the applicable rules in hand â e.g. an XBRL typed-dimension member's own
|
|
4352
|
+
* `validation` block, embedded directly on the hypercube JSON's `typedMember` (a separate
|
|
4353
|
+
* source from, and not necessarily mirrored in, datatypes.json â many typed-member types
|
|
4354
|
+
* such as "nl-types:string255" never appear there at all).
|
|
4355
|
+
*/
|
|
4356
|
+
static validateAgainstRules(value, validation2) {
|
|
4357
|
+
const errors = [];
|
|
4358
|
+
if (value === null || value === void 0 || value === "") {
|
|
4359
|
+
return { valid: true, errors: [] };
|
|
4360
|
+
}
|
|
4361
|
+
if (!validation2) {
|
|
4362
|
+
return { valid: true, errors: [] };
|
|
4363
|
+
}
|
|
4364
|
+
const stringValue = String(value);
|
|
4365
|
+
if (validation2.pattern) {
|
|
4366
|
+
const patternError = this.validatePattern(stringValue, validation2.pattern);
|
|
4367
|
+
if (patternError) {
|
|
4368
|
+
errors.push(patternError);
|
|
4369
|
+
}
|
|
4370
|
+
}
|
|
4371
|
+
if (validation2.length !== void 0) {
|
|
4372
|
+
const lengthError = this.validateExactLength(stringValue, validation2.length);
|
|
4373
|
+
if (lengthError) {
|
|
4374
|
+
errors.push(lengthError);
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
if (validation2.minLength !== void 0) {
|
|
4378
|
+
const minLengthError = this.validateMinLength(stringValue, validation2.minLength);
|
|
4379
|
+
if (minLengthError) {
|
|
4380
|
+
errors.push(minLengthError);
|
|
4381
|
+
}
|
|
4382
|
+
}
|
|
4383
|
+
if (validation2.maxLength !== void 0) {
|
|
4384
|
+
const maxLengthError = this.validateMaxLength(stringValue, validation2.maxLength);
|
|
4385
|
+
if (maxLengthError) {
|
|
4386
|
+
errors.push(maxLengthError);
|
|
4387
|
+
}
|
|
4388
|
+
}
|
|
4389
|
+
if (validation2.totalDigits !== void 0) {
|
|
4390
|
+
const totalDigitsError = this.validateTotalDigits(stringValue, validation2.totalDigits);
|
|
4391
|
+
if (totalDigitsError) {
|
|
4392
|
+
errors.push(totalDigitsError);
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
4395
|
+
return {
|
|
4396
|
+
valid: errors.length === 0,
|
|
4397
|
+
errors
|
|
4398
|
+
};
|
|
4399
|
+
}
|
|
4400
|
+
/**
|
|
4401
|
+
* Finds the datatype definition for a given concept type
|
|
4402
|
+
* Handles both exact matches and base type chain resolution
|
|
4403
|
+
*/
|
|
4404
|
+
static findDatatype(conceptType, datatypes) {
|
|
4405
|
+
let datatype = datatypes.find((dt) => dt.type === conceptType);
|
|
4406
|
+
if (datatype) {
|
|
4407
|
+
return datatype;
|
|
4408
|
+
}
|
|
4409
|
+
const visited = /* @__PURE__ */ new Set();
|
|
4410
|
+
let currentType = conceptType;
|
|
4411
|
+
while (currentType && !visited.has(currentType)) {
|
|
4412
|
+
visited.add(currentType);
|
|
4413
|
+
datatype = datatypes.find((dt) => dt.type === currentType);
|
|
4414
|
+
if (datatype) {
|
|
4415
|
+
if (datatype.validation) {
|
|
4416
|
+
return datatype;
|
|
4417
|
+
}
|
|
4418
|
+
currentType = datatype.basetype;
|
|
4419
|
+
} else {
|
|
4420
|
+
break;
|
|
4421
|
+
}
|
|
4422
|
+
}
|
|
4423
|
+
return void 0;
|
|
4424
|
+
}
|
|
4425
|
+
/**
|
|
4426
|
+
* Validates that value matches the regex pattern
|
|
4427
|
+
*/
|
|
4428
|
+
static validatePattern(value, pattern) {
|
|
4429
|
+
try {
|
|
4430
|
+
const regex = new RegExp(`^${pattern}$`);
|
|
4431
|
+
if (!regex.test(value)) {
|
|
4432
|
+
return {
|
|
4433
|
+
type: "pattern",
|
|
4434
|
+
message: I18n.t("xbrlValidation.patternMismatch", { pattern }),
|
|
4435
|
+
expectedValue: pattern,
|
|
4436
|
+
actualValue: value
|
|
4437
|
+
};
|
|
4438
|
+
}
|
|
4439
|
+
} catch (error2) {
|
|
4440
|
+
console.error(`Invalid regex pattern: ${pattern}`, error2);
|
|
4441
|
+
return {
|
|
4442
|
+
type: "pattern",
|
|
4443
|
+
message: I18n.t("xbrlValidation.invalidPattern", { pattern }),
|
|
4444
|
+
expectedValue: pattern,
|
|
4445
|
+
actualValue: value
|
|
4446
|
+
};
|
|
4447
|
+
}
|
|
4448
|
+
return null;
|
|
4449
|
+
}
|
|
4450
|
+
/**
|
|
4451
|
+
* Validates that value has exact length
|
|
4452
|
+
*/
|
|
4453
|
+
static validateExactLength(value, requiredLength) {
|
|
4454
|
+
if (value.length !== requiredLength) {
|
|
4455
|
+
return {
|
|
4456
|
+
type: "length",
|
|
4457
|
+
message: I18n.t("xbrlValidation.exactLength", { required: requiredLength, current: value.length }),
|
|
4458
|
+
expectedValue: requiredLength,
|
|
4459
|
+
actualValue: value.length
|
|
4460
|
+
};
|
|
4461
|
+
}
|
|
4462
|
+
return null;
|
|
4463
|
+
}
|
|
4464
|
+
/**
|
|
4465
|
+
* Validates minimum length
|
|
4466
|
+
*/
|
|
4467
|
+
static validateMinLength(value, minLength) {
|
|
4468
|
+
if (value.length < minLength) {
|
|
4469
|
+
return {
|
|
4470
|
+
type: "minLength",
|
|
4471
|
+
message: I18n.t("xbrlValidation.minLength", { required: minLength, current: value.length }),
|
|
4472
|
+
expectedValue: minLength,
|
|
4473
|
+
actualValue: value.length
|
|
4474
|
+
};
|
|
4475
|
+
}
|
|
4476
|
+
return null;
|
|
4477
|
+
}
|
|
4478
|
+
/**
|
|
4479
|
+
* Validates maximum length
|
|
4480
|
+
*/
|
|
4481
|
+
static validateMaxLength(value, maxLength) {
|
|
4482
|
+
if (value.length > maxLength) {
|
|
4483
|
+
return {
|
|
4484
|
+
type: "maxLength",
|
|
4485
|
+
message: I18n.t("xbrlValidation.maxLength", { required: maxLength, current: value.length }),
|
|
4486
|
+
expectedValue: maxLength,
|
|
4487
|
+
actualValue: value.length
|
|
4488
|
+
};
|
|
4489
|
+
}
|
|
4490
|
+
return null;
|
|
4491
|
+
}
|
|
4492
|
+
/**
|
|
4493
|
+
* Validates total digits (for numeric values)
|
|
4494
|
+
*/
|
|
4495
|
+
static validateTotalDigits(value, totalDigits) {
|
|
4496
|
+
const digitsOnly = value.replace(/[^\d]/g, "");
|
|
4497
|
+
if (digitsOnly.length > totalDigits) {
|
|
4202
4498
|
return {
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
step: 0.01,
|
|
4208
|
-
placeholder: I18n.t("field.enterCurrency")
|
|
4499
|
+
type: "totalDigits",
|
|
4500
|
+
message: I18n.t("xbrlValidation.totalDigits", { required: totalDigits, current: digitsOnly.length }),
|
|
4501
|
+
expectedValue: totalDigits,
|
|
4502
|
+
actualValue: digitsOnly.length
|
|
4209
4503
|
};
|
|
4210
4504
|
}
|
|
4211
|
-
return
|
|
4212
|
-
fieldType: "decimal",
|
|
4213
|
-
htmlInputType: "text",
|
|
4214
|
-
inputMode: "decimal",
|
|
4215
|
-
allowDecimals: true,
|
|
4216
|
-
step: "any",
|
|
4217
|
-
placeholder: I18n.t("field.enterDecimalValue")
|
|
4218
|
-
};
|
|
4505
|
+
return null;
|
|
4219
4506
|
}
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4507
|
+
/**
|
|
4508
|
+
* Gets a human-readable summary of validation rules for a concept type
|
|
4509
|
+
* Useful for displaying validation hints to users
|
|
4510
|
+
*/
|
|
4511
|
+
static getValidationRulesSummary(conceptType, datatypes) {
|
|
4512
|
+
if (!conceptType || !datatypes) {
|
|
4513
|
+
return [];
|
|
4514
|
+
}
|
|
4515
|
+
const datatype = this.findDatatype(conceptType, datatypes);
|
|
4516
|
+
if (!datatype || !datatype.validation) {
|
|
4517
|
+
return [];
|
|
4518
|
+
}
|
|
4519
|
+
const validation2 = datatype.validation;
|
|
4520
|
+
const rules = [];
|
|
4521
|
+
if (validation2.pattern) {
|
|
4522
|
+
rules.push(I18n.t("xbrlValidation.ruleSummaryPattern", { pattern: validation2.pattern }));
|
|
4523
|
+
}
|
|
4524
|
+
if (validation2.length !== void 0) {
|
|
4525
|
+
rules.push(I18n.t("xbrlValidation.ruleSummaryExactLength", { required: validation2.length }));
|
|
4526
|
+
}
|
|
4527
|
+
if (validation2.minLength !== void 0) {
|
|
4528
|
+
rules.push(I18n.t("xbrlValidation.ruleSummaryMinLength", { required: validation2.minLength }));
|
|
4529
|
+
}
|
|
4530
|
+
if (validation2.maxLength !== void 0) {
|
|
4531
|
+
rules.push(I18n.t("xbrlValidation.ruleSummaryMaxLength", { required: validation2.maxLength }));
|
|
4532
|
+
}
|
|
4533
|
+
if (validation2.totalDigits !== void 0) {
|
|
4534
|
+
rules.push(I18n.t("xbrlValidation.ruleSummaryTotalDigits", { required: validation2.totalDigits }));
|
|
4535
|
+
}
|
|
4536
|
+
return rules;
|
|
4226
4537
|
}
|
|
4227
|
-
return {
|
|
4228
|
-
fieldType: "text",
|
|
4229
|
-
htmlInputType: "text",
|
|
4230
|
-
placeholder: I18n.t("field.enterValue")
|
|
4231
|
-
};
|
|
4232
4538
|
}
|
|
4233
4539
|
var __defProp$7 = Object.defineProperty;
|
|
4234
4540
|
var __getOwnPropDesc$7 = Object.getOwnPropertyDescriptor;
|
|
@@ -4310,6 +4616,14 @@ let JupiterFormField = class extends LitElement {
|
|
|
4310
4616
|
}
|
|
4311
4617
|
_handleInput(event) {
|
|
4312
4618
|
const target = event.target;
|
|
4619
|
+
const isDateType = target instanceof HTMLInputElement && (target.type === "date" || target.type === "datetime-local");
|
|
4620
|
+
if (isDateType) {
|
|
4621
|
+
if (event.type !== "change") {
|
|
4622
|
+
return;
|
|
4623
|
+
}
|
|
4624
|
+
} else if (target.tagName === "INPUT" && target.type !== "checkbox" && event.type === "change") {
|
|
4625
|
+
return;
|
|
4626
|
+
}
|
|
4313
4627
|
const oldValue = this.value;
|
|
4314
4628
|
let value = target.value;
|
|
4315
4629
|
if (this._isNumericField()) {
|
|
@@ -4447,7 +4761,7 @@ let JupiterFormField = class extends LitElement {
|
|
|
4447
4761
|
return;
|
|
4448
4762
|
}
|
|
4449
4763
|
const typeConfig = getInputTypeForConceptType(this.conceptType, this.datatypes);
|
|
4450
|
-
const allowNegative = typeConfig.min === void 0 || typeConfig.min < 0;
|
|
4764
|
+
const allowNegative = typeConfig.min === void 0 || typeof typeConfig.min === "number" && typeConfig.min < 0;
|
|
4451
4765
|
if (event.key === "-" && allowNegative && !target.value.includes("-") && (target.selectionStart === 0 || target.value.length === 0)) {
|
|
4452
4766
|
return;
|
|
4453
4767
|
}
|
|
@@ -4941,6 +5255,33 @@ let JupiterFormField = class extends LitElement {
|
|
|
4941
5255
|
} else {
|
|
4942
5256
|
renderedInputValue = effectiveValue ?? "";
|
|
4943
5257
|
}
|
|
5258
|
+
if (inputType === "date" || inputType === "datetime-local") {
|
|
5259
|
+
return html`
|
|
5260
|
+
<div class="date-wrapper">
|
|
5261
|
+
<input
|
|
5262
|
+
id="${fieldId}"
|
|
5263
|
+
name="${fieldName}"
|
|
5264
|
+
type="${inputType}"
|
|
5265
|
+
inputmode="${typeConfig.inputMode || ""}"
|
|
5266
|
+
class="${cssClass}"
|
|
5267
|
+
.value="${renderedInputValue}"
|
|
5268
|
+
?disabled="${effectiveDisabled || this.field.disabled}"
|
|
5269
|
+
placeholder="${typeConfig.placeholder || this.field.placeholder || ""}"
|
|
5270
|
+
min="${min !== void 0 ? min : ""}"
|
|
5271
|
+
max="${max !== void 0 ? max : ""}"
|
|
5272
|
+
@input="${this._handleInput}"
|
|
5273
|
+
@change="${this._handleInput}"
|
|
5274
|
+
@focus="${this._handleFocus}"
|
|
5275
|
+
@blur="${this._handleBlur}"
|
|
5276
|
+
/>
|
|
5277
|
+
<svg class="calendar-icon-svg" viewBox="0 0 24 24">
|
|
5278
|
+
<path fill="currentColor" d="M19 4h-1V2h-2v2H8V2H6v2H5
|
|
5279
|
+
a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14
|
|
5280
|
+
a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 16H5V9h14z"/>
|
|
5281
|
+
</svg>
|
|
5282
|
+
</div>
|
|
5283
|
+
`;
|
|
5284
|
+
}
|
|
4944
5285
|
return html`
|
|
4945
5286
|
<input
|
|
4946
5287
|
id="${fieldId}"
|
|
@@ -4955,6 +5296,7 @@ let JupiterFormField = class extends LitElement {
|
|
|
4955
5296
|
min="${min !== void 0 ? min : ""}"
|
|
4956
5297
|
max="${max !== void 0 ? max : ""}"
|
|
4957
5298
|
@input="${this._handleInput}"
|
|
5299
|
+
@change="${this._handleInput}"
|
|
4958
5300
|
@focus="${this._handleFocus}"
|
|
4959
5301
|
@blur="${this._handleBlur}"
|
|
4960
5302
|
@keydown="${this._handleKeyDown}"
|
|
@@ -5007,6 +5349,8 @@ let JupiterFormField = class extends LitElement {
|
|
|
5007
5349
|
<div class="date-wrapper">
|
|
5008
5350
|
<input
|
|
5009
5351
|
type="date"
|
|
5352
|
+
min="1900-01-01"
|
|
5353
|
+
max="2100-12-31"
|
|
5010
5354
|
.value="${this.periodInstantDate || this.field.periodInstantDate || ""}"
|
|
5011
5355
|
@change="${(e2) => this._handlePeriodChange(e2, "instant")}"
|
|
5012
5356
|
?disabled="${this.disabled}"
|
|
@@ -5026,6 +5370,8 @@ let JupiterFormField = class extends LitElement {
|
|
|
5026
5370
|
<div class="date-wrapper">
|
|
5027
5371
|
<input
|
|
5028
5372
|
type="date"
|
|
5373
|
+
min="1900-01-01"
|
|
5374
|
+
max="2100-12-31"
|
|
5029
5375
|
.value="${this.periodStartDate || this.field.periodStartDate || ""}"
|
|
5030
5376
|
@change="${(e2) => this._handlePeriodChange(e2, "start")}"
|
|
5031
5377
|
?disabled="${this.disabled}"
|
|
@@ -5040,6 +5386,8 @@ let JupiterFormField = class extends LitElement {
|
|
|
5040
5386
|
<div class="date-wrapper">
|
|
5041
5387
|
<input
|
|
5042
5388
|
type="date"
|
|
5389
|
+
min="1900-01-01"
|
|
5390
|
+
max="2100-12-31"
|
|
5043
5391
|
.value="${this.periodEndDate || this.field.periodEndDate || ""}"
|
|
5044
5392
|
@change="${(e2) => this._handlePeriodChange(e2, "end")}"
|
|
5045
5393
|
?disabled="${this.disabled}"
|
|
@@ -5073,6 +5421,8 @@ let JupiterFormField = class extends LitElement {
|
|
|
5073
5421
|
<div class="date-wrapper">
|
|
5074
5422
|
<input
|
|
5075
5423
|
type="date"
|
|
5424
|
+
min="1900-01-01"
|
|
5425
|
+
max="2100-12-31"
|
|
5076
5426
|
.value="${this.periodInstantDate || this.field.periodInstantDate || ""}"
|
|
5077
5427
|
@change="${(e2) => this._handlePeriodChange(e2, "instant")}"
|
|
5078
5428
|
?disabled="${this.disabled}"
|
|
@@ -5090,6 +5440,8 @@ let JupiterFormField = class extends LitElement {
|
|
|
5090
5440
|
<div class="date-wrapper">
|
|
5091
5441
|
<input
|
|
5092
5442
|
type="date"
|
|
5443
|
+
min="1900-01-01"
|
|
5444
|
+
max="2100-12-31"
|
|
5093
5445
|
.value="${this.periodStartDate || this.field.periodStartDate || ""}"
|
|
5094
5446
|
@change="${(e2) => this._handlePeriodChange(e2, "start")}"
|
|
5095
5447
|
?disabled="${this.disabled}"
|
|
@@ -5108,6 +5460,8 @@ let JupiterFormField = class extends LitElement {
|
|
|
5108
5460
|
<div class="date-wrapper">
|
|
5109
5461
|
<input
|
|
5110
5462
|
type="date"
|
|
5463
|
+
min="1900-01-01"
|
|
5464
|
+
max="2100-12-31"
|
|
5111
5465
|
.value="${this.periodEndDate || this.field.periodEndDate || ""}"
|
|
5112
5466
|
@change="${(e2) => this._handlePeriodChange(e2, "end")}"
|
|
5113
5467
|
?disabled="${this.disabled}"
|
|
@@ -5338,6 +5692,11 @@ JupiterFormField.styles = css`
|
|
|
5338
5692
|
border-radius: 4px;
|
|
5339
5693
|
transition: background-color 0.2s;
|
|
5340
5694
|
opacity: 0.7;
|
|
5695
|
+
/* Keep this icon on the wrapper's right edge (same rail as every other field type)
|
|
5696
|
+
regardless of the sibling input's own width â the date-wrapper next to it sizes to
|
|
5697
|
+
its dd/mm/yyyy content, which is narrower than a full-width text/select input. */
|
|
5698
|
+
margin-left: auto;
|
|
5699
|
+
flex-shrink: 0;
|
|
5341
5700
|
}
|
|
5342
5701
|
|
|
5343
5702
|
.period-icon-btn:hover {
|
|
@@ -6600,6 +6959,7 @@ let JupiterAddColumnDialog = class extends LitElement {
|
|
|
6600
6959
|
this._instantDate = "";
|
|
6601
6960
|
this._selectedType = "duration";
|
|
6602
6961
|
this._selectedDimensions = /* @__PURE__ */ new Map();
|
|
6962
|
+
this._typedValueErrors = {};
|
|
6603
6963
|
}
|
|
6604
6964
|
updated(changedProperties) {
|
|
6605
6965
|
if (changedProperties.has("open") && this.open) {
|
|
@@ -6663,6 +7023,7 @@ let JupiterAddColumnDialog = class extends LitElement {
|
|
|
6663
7023
|
this._instantDate = e2.target.value;
|
|
6664
7024
|
}
|
|
6665
7025
|
_handleDimensionToggle(dimensionId, event) {
|
|
7026
|
+
var _a, _b;
|
|
6666
7027
|
const checkbox = event.target;
|
|
6667
7028
|
if (checkbox.checked) {
|
|
6668
7029
|
const dimension = this.availableDimensions.find((d2) => d2.id === dimensionId);
|
|
@@ -6670,7 +7031,9 @@ let JupiterAddColumnDialog = class extends LitElement {
|
|
|
6670
7031
|
const selection = {
|
|
6671
7032
|
axisId: dimension.id,
|
|
6672
7033
|
axisLabel: dimension.axisLabel,
|
|
6673
|
-
isTyped: !!(dimension.typedMember && (!dimension.members || dimension.members.length === 0))
|
|
7034
|
+
isTyped: !!(dimension.typedMember && (!dimension.members || dimension.members.length === 0)),
|
|
7035
|
+
dataType: (_a = dimension.typedMember) == null ? void 0 : _a.dataType,
|
|
7036
|
+
validation: (_b = dimension.typedMember) == null ? void 0 : _b.validation
|
|
6674
7037
|
};
|
|
6675
7038
|
this._selectedDimensions.set(dimensionId, selection);
|
|
6676
7039
|
}
|
|
@@ -6689,24 +7052,130 @@ let JupiterAddColumnDialog = class extends LitElement {
|
|
|
6689
7052
|
}
|
|
6690
7053
|
}
|
|
6691
7054
|
_handleTypedValueChange(dimensionId, event) {
|
|
6692
|
-
const
|
|
7055
|
+
const target = event.target;
|
|
7056
|
+
const value = target instanceof HTMLInputElement && target.type === "checkbox" ? String(target.checked) : target.value;
|
|
6693
7057
|
const existing = this._selectedDimensions.get(dimensionId);
|
|
6694
7058
|
if (existing) {
|
|
6695
|
-
existing.typedValue =
|
|
7059
|
+
existing.typedValue = value;
|
|
6696
7060
|
this._selectedDimensions.set(dimensionId, existing);
|
|
6697
7061
|
}
|
|
6698
7062
|
}
|
|
7063
|
+
/**
|
|
7064
|
+
* Validate a typed dimension's value against its XBRL datatype's `validation` constraints
|
|
7065
|
+
* (minLength/maxLength/length/pattern/totalDigits), reusing the exact same datatype-driven
|
|
7066
|
+
* validator concept row fields use on blur (`XBRLValidator.validateConceptValue` â see
|
|
7067
|
+
* `form-field.ts`'s `_validateXBRLDatatype` / `form-section.ts`'s `_validateTypedMemberValue`).
|
|
7068
|
+
*/
|
|
7069
|
+
_validateTypedValue(dimensionId, dataType, validation2, value) {
|
|
7070
|
+
const hasExistingEntry = dimensionId in this._typedValueErrors;
|
|
7071
|
+
if (!value) {
|
|
7072
|
+
if (hasExistingEntry) {
|
|
7073
|
+
const updated = { ...this._typedValueErrors };
|
|
7074
|
+
delete updated[dimensionId];
|
|
7075
|
+
this._typedValueErrors = updated;
|
|
7076
|
+
}
|
|
7077
|
+
return;
|
|
7078
|
+
}
|
|
7079
|
+
const result = validation2 ? XBRLValidator.validateAgainstRules(value, validation2) : XBRLValidator.validateConceptValue(dataType, value, this.datatypes);
|
|
7080
|
+
if (result.valid) {
|
|
7081
|
+
if (hasExistingEntry) {
|
|
7082
|
+
const updated = { ...this._typedValueErrors };
|
|
7083
|
+
delete updated[dimensionId];
|
|
7084
|
+
this._typedValueErrors = updated;
|
|
7085
|
+
}
|
|
7086
|
+
} else {
|
|
7087
|
+
this._typedValueErrors = { ...this._typedValueErrors, [dimensionId]: result.errors };
|
|
7088
|
+
}
|
|
7089
|
+
}
|
|
7090
|
+
_handleTypedValueBlur(dimensionId, dataType, validation2, event) {
|
|
7091
|
+
const target = event.target;
|
|
7092
|
+
const value = target instanceof HTMLInputElement && target.type === "checkbox" ? String(target.checked) : target.value;
|
|
7093
|
+
this._validateTypedValue(dimensionId, dataType, validation2, value);
|
|
7094
|
+
}
|
|
7095
|
+
/** Renders the `field-errors`-style message list for a typed dimension's value input, if any. */
|
|
7096
|
+
_renderTypedValueErrors(dimensionId) {
|
|
7097
|
+
const errors = this._typedValueErrors[dimensionId];
|
|
7098
|
+
if (!errors || errors.length === 0)
|
|
7099
|
+
return "";
|
|
7100
|
+
return html`
|
|
7101
|
+
<div class="typed-input-errors">
|
|
7102
|
+
${errors.map((error2) => html`<span class="typed-input-error">${error2.message}</span>`)}
|
|
7103
|
+
</div>
|
|
7104
|
+
`;
|
|
7105
|
+
}
|
|
7106
|
+
/**
|
|
7107
|
+
* Resolve the input-type configuration for a typed dimension's value input, reusing the same
|
|
7108
|
+
* XBRL type â HTML input mapping that drives concept row fields and the column-header typed-
|
|
7109
|
+
* member input (see `getInputTypeForConceptType` / `form-field.ts`'s `_renderInput` /
|
|
7110
|
+
* `form-section.ts`'s `_renderTypedMemberHeaderInput`).
|
|
7111
|
+
*/
|
|
7112
|
+
_getTypedValueInputConfig(dataType) {
|
|
7113
|
+
return getInputTypeForConceptType(dataType, this.datatypes);
|
|
7114
|
+
}
|
|
7115
|
+
/**
|
|
7116
|
+
* Render the typed dimension's value input, choosing select/checkbox/date/generic text-like
|
|
7117
|
+
* input based on the typed member's XBRL datatype instead of always rendering plain text.
|
|
7118
|
+
*/
|
|
7119
|
+
_renderTypedValueInput(dimensionId, dataType, validation2, currentValue) {
|
|
7120
|
+
var _a;
|
|
7121
|
+
const typeConfig = this._getTypedValueInputConfig(dataType);
|
|
7122
|
+
const hasValidationError = !!((_a = this._typedValueErrors[dimensionId]) == null ? void 0 : _a.length);
|
|
7123
|
+
const inputClass = `typed-input ${hasValidationError ? "has-error" : ""}`;
|
|
7124
|
+
const onChange = (e2) => this._handleTypedValueChange(dimensionId, e2);
|
|
7125
|
+
const onBlur = (e2) => this._handleTypedValueBlur(dimensionId, dataType, validation2, e2);
|
|
7126
|
+
if (typeConfig.fieldType === "select" || isSelectType(typeConfig.fieldType)) {
|
|
7127
|
+
const options = typeConfig.enumerations || [];
|
|
7128
|
+
return html`
|
|
7129
|
+
<select class="${inputClass}" .value="${currentValue}" @change="${onChange}" @blur="${onBlur}">
|
|
7130
|
+
<option value="">${typeConfig.placeholder || I18n.t("field.select")}</option>
|
|
7131
|
+
${options.map((option) => html`
|
|
7132
|
+
<option value="${option.value}" ?selected="${currentValue === option.value}">
|
|
7133
|
+
${option.label || option.value}
|
|
7134
|
+
</option>
|
|
7135
|
+
`)}
|
|
7136
|
+
</select>
|
|
7137
|
+
`;
|
|
7138
|
+
}
|
|
7139
|
+
if (typeConfig.fieldType === "boolean" || isCheckboxType(typeConfig.fieldType)) {
|
|
7140
|
+
return html`
|
|
7141
|
+
<input
|
|
7142
|
+
type="checkbox"
|
|
7143
|
+
class="${inputClass}"
|
|
7144
|
+
.checked="${currentValue === "true"}"
|
|
7145
|
+
@change="${onChange}"
|
|
7146
|
+
@blur="${onBlur}"
|
|
7147
|
+
/>
|
|
7148
|
+
`;
|
|
7149
|
+
}
|
|
7150
|
+
const inputType = typeConfig.htmlInputType || "text";
|
|
7151
|
+
return html`
|
|
7152
|
+
<input
|
|
7153
|
+
type="${inputType}"
|
|
7154
|
+
inputmode="${typeConfig.inputMode || ""}"
|
|
7155
|
+
class="${inputClass}"
|
|
7156
|
+
.value="${currentValue}"
|
|
7157
|
+
min="${typeConfig.min !== void 0 ? typeConfig.min : ""}"
|
|
7158
|
+
max="${typeConfig.max !== void 0 ? typeConfig.max : ""}"
|
|
7159
|
+
step="${typeConfig.step !== void 0 ? typeConfig.step : ""}"
|
|
7160
|
+
@input="${onChange}"
|
|
7161
|
+
@blur="${onBlur}"
|
|
7162
|
+
placeholder="${typeConfig.placeholder || I18n.t("column.enterValuePlaceholder")}"
|
|
7163
|
+
/>
|
|
7164
|
+
`;
|
|
7165
|
+
}
|
|
6699
7166
|
_resetForm() {
|
|
6700
7167
|
this._startDate = this.periodStartDate || "";
|
|
6701
7168
|
this._endDate = this.periodEndDate || "";
|
|
6702
7169
|
this._instantDate = this.periodEndDate || this.periodStartDate || "";
|
|
6703
7170
|
this._selectedType = this.periodType === "instant" ? "instant" : "duration";
|
|
6704
7171
|
this._selectedDimensions.clear();
|
|
7172
|
+
this._typedValueErrors = {};
|
|
6705
7173
|
this._autoSelectSingleMemberDimensions();
|
|
6706
7174
|
this.requestUpdate();
|
|
6707
7175
|
}
|
|
6708
7176
|
_autoSelectSingleMemberDimensions() {
|
|
6709
7177
|
this.availableDimensions.forEach((dimension) => {
|
|
7178
|
+
var _a, _b;
|
|
6710
7179
|
if (dimension.members && dimension.members.length > 0) {
|
|
6711
7180
|
const member = dimension.members[0];
|
|
6712
7181
|
const selection = {
|
|
@@ -6722,7 +7191,9 @@ let JupiterAddColumnDialog = class extends LitElement {
|
|
|
6722
7191
|
const selection = {
|
|
6723
7192
|
axisId: dimension.id,
|
|
6724
7193
|
axisLabel: dimension.axisLabel,
|
|
6725
|
-
isTyped: true
|
|
7194
|
+
isTyped: true,
|
|
7195
|
+
dataType: (_a = dimension.typedMember) == null ? void 0 : _a.dataType,
|
|
7196
|
+
validation: (_b = dimension.typedMember) == null ? void 0 : _b.validation
|
|
6726
7197
|
// Note: typedValue will be entered in column header, not here
|
|
6727
7198
|
};
|
|
6728
7199
|
this._selectedDimensions.set(dimension.id, selection);
|
|
@@ -6748,6 +7219,8 @@ let JupiterAddColumnDialog = class extends LitElement {
|
|
|
6748
7219
|
<div class="date-wrapper">
|
|
6749
7220
|
<input
|
|
6750
7221
|
type="date"
|
|
7222
|
+
min="1900-01-01"
|
|
7223
|
+
max="2100-12-31"
|
|
6751
7224
|
class="form-input"
|
|
6752
7225
|
.value="${this._instantDate}"
|
|
6753
7226
|
@change="${this._handleInstantDateChange}"
|
|
@@ -6767,6 +7240,8 @@ let JupiterAddColumnDialog = class extends LitElement {
|
|
|
6767
7240
|
<div class="date-wrapper">
|
|
6768
7241
|
<input
|
|
6769
7242
|
type="date"
|
|
7243
|
+
min="1900-01-01"
|
|
7244
|
+
max="2100-12-31"
|
|
6770
7245
|
class="form-input"
|
|
6771
7246
|
.value="${this._startDate}"
|
|
6772
7247
|
@change="${this._handleStartDateChange}"
|
|
@@ -6784,6 +7259,8 @@ let JupiterAddColumnDialog = class extends LitElement {
|
|
|
6784
7259
|
<div class="date-wrapper">
|
|
6785
7260
|
<input
|
|
6786
7261
|
type="date"
|
|
7262
|
+
min="1900-01-01"
|
|
7263
|
+
max="2100-12-31"
|
|
6787
7264
|
class="form-input"
|
|
6788
7265
|
.value="${this._endDate}"
|
|
6789
7266
|
@change="${this._handleEndDateChange}"
|
|
@@ -6806,6 +7283,7 @@ let JupiterAddColumnDialog = class extends LitElement {
|
|
|
6806
7283
|
<p class="form-description">${I18n.t("column.dimensionsDescription")}</p>
|
|
6807
7284
|
|
|
6808
7285
|
${this.availableDimensions.map((dimension) => {
|
|
7286
|
+
var _a, _b;
|
|
6809
7287
|
const isSelected = this._selectedDimensions.has(dimension.id);
|
|
6810
7288
|
const selection = this._selectedDimensions.get(dimension.id);
|
|
6811
7289
|
const isTypedDimension = !!(dimension.typedMember && (!dimension.members || dimension.members.length === 0));
|
|
@@ -6830,13 +7308,8 @@ let JupiterAddColumnDialog = class extends LitElement {
|
|
|
6830
7308
|
<!-- Typed Dimension Input -->
|
|
6831
7309
|
<div class="member-selection">
|
|
6832
7310
|
<label>${I18n.t("column.enterValue")} ${dimension.conceptName}:</label>
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
class="typed-input"
|
|
6836
|
-
.value="${(selection == null ? void 0 : selection.typedValue) || ""}"
|
|
6837
|
-
@input="${(e2) => this._handleTypedValueChange(dimension.id, e2)}"
|
|
6838
|
-
placeholder="${I18n.t("column.enterValuePlaceholder")}"
|
|
6839
|
-
/>
|
|
7311
|
+
${this._renderTypedValueInput(dimension.id, (_a = dimension.typedMember) == null ? void 0 : _a.dataType, (_b = dimension.typedMember) == null ? void 0 : _b.validation, (selection == null ? void 0 : selection.typedValue) || "")}
|
|
7312
|
+
${this._renderTypedValueErrors(dimension.id)}
|
|
6840
7313
|
</div>
|
|
6841
7314
|
` : html`
|
|
6842
7315
|
<!-- Domain Members Selection -->
|
|
@@ -7153,6 +7626,20 @@ JupiterAddColumnDialog.styles = css`
|
|
|
7153
7626
|
font-size: 14px;
|
|
7154
7627
|
}
|
|
7155
7628
|
|
|
7629
|
+
.typed-input.has-error {
|
|
7630
|
+
border-color: var(--jupiter-error-color, #d32f2f);
|
|
7631
|
+
}
|
|
7632
|
+
|
|
7633
|
+
.typed-input-errors {
|
|
7634
|
+
margin-top: 4px;
|
|
7635
|
+
}
|
|
7636
|
+
|
|
7637
|
+
.typed-input-error {
|
|
7638
|
+
display: block;
|
|
7639
|
+
font-size: 12px;
|
|
7640
|
+
color: var(--jupiter-error-color, #d32f2f);
|
|
7641
|
+
}
|
|
7642
|
+
|
|
7156
7643
|
.member-list {
|
|
7157
7644
|
max-height: 120px;
|
|
7158
7645
|
overflow-y: auto;
|
|
@@ -7208,6 +7695,9 @@ __decorateClass$5([
|
|
|
7208
7695
|
__decorateClass$5([
|
|
7209
7696
|
n2({ type: String })
|
|
7210
7697
|
], JupiterAddColumnDialog.prototype, "periodEndDate", 2);
|
|
7698
|
+
__decorateClass$5([
|
|
7699
|
+
n2({ type: Array })
|
|
7700
|
+
], JupiterAddColumnDialog.prototype, "datatypes", 2);
|
|
7211
7701
|
__decorateClass$5([
|
|
7212
7702
|
r()
|
|
7213
7703
|
], JupiterAddColumnDialog.prototype, "_startDate", 2);
|
|
@@ -7223,6 +7713,9 @@ __decorateClass$5([
|
|
|
7223
7713
|
__decorateClass$5([
|
|
7224
7714
|
r()
|
|
7225
7715
|
], JupiterAddColumnDialog.prototype, "_selectedDimensions", 2);
|
|
7716
|
+
__decorateClass$5([
|
|
7717
|
+
r()
|
|
7718
|
+
], JupiterAddColumnDialog.prototype, "_typedValueErrors", 2);
|
|
7226
7719
|
JupiterAddColumnDialog = __decorateClass$5([
|
|
7227
7720
|
t$1("jupiter-add-column-dialog")
|
|
7228
7721
|
], JupiterAddColumnDialog);
|
|
@@ -7267,6 +7760,7 @@ let JupiterFormSection = class extends LitElement {
|
|
|
7267
7760
|
this._sectionPeriodType = "duration";
|
|
7268
7761
|
this._openMenuColumnId = null;
|
|
7269
7762
|
this._insertAfterColumnId = null;
|
|
7763
|
+
this._typedMemberErrors = {};
|
|
7270
7764
|
this._boundFieldBlur = (e2) => {
|
|
7271
7765
|
this._clearHighlights();
|
|
7272
7766
|
this._focusedConceptId = null;
|
|
@@ -7888,9 +8382,72 @@ let JupiterFormSection = class extends LitElement {
|
|
|
7888
8382
|
var _a;
|
|
7889
8383
|
return ((_a = this.typedMemberData[columnId]) == null ? void 0 : _a[axisId]) || "";
|
|
7890
8384
|
}
|
|
8385
|
+
/** True once any concept in this section has a non-empty value entered for the given column. */
|
|
8386
|
+
_columnHasAnyValue(columnId) {
|
|
8387
|
+
return Object.keys(this.formData).some((conceptId) => {
|
|
8388
|
+
var _a;
|
|
8389
|
+
const value = (_a = this.formData[conceptId]) == null ? void 0 : _a[columnId];
|
|
8390
|
+
return value !== void 0 && value !== null && value !== "";
|
|
8391
|
+
});
|
|
8392
|
+
}
|
|
8393
|
+
/**
|
|
8394
|
+
* Resolve the input-type configuration for a typed dimension's column-header input,
|
|
8395
|
+
* reusing the same XBRL type â HTML input mapping that drives concept row fields
|
|
8396
|
+
* (see `getInputTypeForConceptType` / `form-field.ts`'s `_renderInput`).
|
|
8397
|
+
*/
|
|
8398
|
+
_getTypedMemberInputConfig(typedMember) {
|
|
8399
|
+
return getInputTypeForConceptType(typedMember.type, this.datatypes);
|
|
8400
|
+
}
|
|
8401
|
+
_typedMemberErrorKey(columnId, axisId) {
|
|
8402
|
+
return `${columnId}::${axisId}`;
|
|
8403
|
+
}
|
|
8404
|
+
/**
|
|
8405
|
+
* Validate a typed-member header value against its XBRL datatype's `validation` constraints
|
|
8406
|
+
* (minLength/maxLength/length/pattern/totalDigits), reusing the exact same datatype-driven
|
|
8407
|
+
* validator concept row fields use on blur (`XBRLValidator.validateConceptValue` â see
|
|
8408
|
+
* `form-field.ts`'s `_validateXBRLDatatype`). Stores/clears the result in `_typedMemberErrors`.
|
|
8409
|
+
*/
|
|
8410
|
+
_validateTypedMemberValue(columnId, typedMember, value) {
|
|
8411
|
+
const key = this._typedMemberErrorKey(columnId, typedMember.axisId);
|
|
8412
|
+
const hasExistingEntry = key in this._typedMemberErrors;
|
|
8413
|
+
if (!value) {
|
|
8414
|
+
if (hasExistingEntry) {
|
|
8415
|
+
const updated = { ...this._typedMemberErrors };
|
|
8416
|
+
delete updated[key];
|
|
8417
|
+
this._typedMemberErrors = updated;
|
|
8418
|
+
}
|
|
8419
|
+
return;
|
|
8420
|
+
}
|
|
8421
|
+
const result = typedMember.validation ? XBRLValidator.validateAgainstRules(value, typedMember.validation) : XBRLValidator.validateConceptValue(typedMember.type, value, this.datatypes);
|
|
8422
|
+
if (result.valid) {
|
|
8423
|
+
if (hasExistingEntry) {
|
|
8424
|
+
const updated = { ...this._typedMemberErrors };
|
|
8425
|
+
delete updated[key];
|
|
8426
|
+
this._typedMemberErrors = updated;
|
|
8427
|
+
}
|
|
8428
|
+
} else {
|
|
8429
|
+
this._typedMemberErrors = { ...this._typedMemberErrors, [key]: result.errors };
|
|
8430
|
+
}
|
|
8431
|
+
}
|
|
8432
|
+
_handleTypedMemberHeaderBlur(event, columnId, typedMember) {
|
|
8433
|
+
const target = event.target;
|
|
8434
|
+
const value = target instanceof HTMLInputElement && target.type === "checkbox" ? String(target.checked) : target.value;
|
|
8435
|
+
this._validateTypedMemberValue(columnId, typedMember, value);
|
|
8436
|
+
}
|
|
8437
|
+
/** Renders the `field-errors`-style message list for a typed-member header input, if any. */
|
|
8438
|
+
_renderTypedMemberHeaderErrors(columnId, axisId) {
|
|
8439
|
+
const errors = this._typedMemberErrors[this._typedMemberErrorKey(columnId, axisId)];
|
|
8440
|
+
if (!errors || errors.length === 0)
|
|
8441
|
+
return "";
|
|
8442
|
+
return html`
|
|
8443
|
+
<div class="typed-member-header-errors">
|
|
8444
|
+
${errors.map((error2) => html`<span class="typed-member-header-error">${error2.message}</span>`)}
|
|
8445
|
+
</div>
|
|
8446
|
+
`;
|
|
8447
|
+
}
|
|
7891
8448
|
_handleTypedMemberHeaderChange(event, columnId, axisId) {
|
|
7892
8449
|
const target = event.target;
|
|
7893
|
-
const value = target.value;
|
|
8450
|
+
const value = target instanceof HTMLInputElement && target.type === "checkbox" ? String(target.checked) : target.value;
|
|
7894
8451
|
console.log(`đ [FormSection] Typed member header change: columnId=${columnId}, axisId=${axisId}, value=${value}`);
|
|
7895
8452
|
this.dispatchEvent(new CustomEvent("typed-member-change", {
|
|
7896
8453
|
detail: {
|
|
@@ -7901,6 +8458,69 @@ let JupiterFormSection = class extends LitElement {
|
|
|
7901
8458
|
bubbles: true
|
|
7902
8459
|
}));
|
|
7903
8460
|
}
|
|
8461
|
+
/**
|
|
8462
|
+
* Render the typed-member column-header input element, choosing select/checkbox/date/generic
|
|
8463
|
+
* text-like input based on the typed member's XBRL datatype â mirrors the branching in
|
|
8464
|
+
* `form-field.ts`'s `_renderInput`, adapted for the compact header cell.
|
|
8465
|
+
*/
|
|
8466
|
+
_renderTypedMemberHeaderInput(columnId, typedMember, isRequired) {
|
|
8467
|
+
var _a;
|
|
8468
|
+
const typeConfig = this._getTypedMemberInputConfig(typedMember);
|
|
8469
|
+
const currentValue = this._getTypedMemberHeaderValue(columnId, typedMember.axisId);
|
|
8470
|
+
const hasValidationError = !!((_a = this._typedMemberErrors[this._typedMemberErrorKey(columnId, typedMember.axisId)]) == null ? void 0 : _a.length);
|
|
8471
|
+
const fieldClass = `typed-member-header-field ${isRequired ? "required-field" : ""} ${hasValidationError ? "has-error" : ""}`;
|
|
8472
|
+
const requiredTitle = isRequired ? I18n.t("validation.typedMemberRequired") : "";
|
|
8473
|
+
const onChange = (e2) => this._handleTypedMemberHeaderChange(e2, columnId, typedMember.axisId);
|
|
8474
|
+
const onBlur = (e2) => this._handleTypedMemberHeaderBlur(e2, columnId, typedMember);
|
|
8475
|
+
if (typeConfig.fieldType === "select" || isSelectType(typeConfig.fieldType)) {
|
|
8476
|
+
const options = typeConfig.enumerations || [];
|
|
8477
|
+
return html`
|
|
8478
|
+
<select
|
|
8479
|
+
class="${fieldClass}"
|
|
8480
|
+
.value="${currentValue}"
|
|
8481
|
+
title="${requiredTitle}"
|
|
8482
|
+
@change="${onChange}"
|
|
8483
|
+
@blur="${onBlur}"
|
|
8484
|
+
>
|
|
8485
|
+
<option value="">${typeConfig.placeholder || I18n.t("field.select")}</option>
|
|
8486
|
+
${options.map((option) => html`
|
|
8487
|
+
<option value="${option.value}" ?selected="${currentValue === option.value}">
|
|
8488
|
+
${option.label || option.value}
|
|
8489
|
+
</option>
|
|
8490
|
+
`)}
|
|
8491
|
+
</select>
|
|
8492
|
+
`;
|
|
8493
|
+
}
|
|
8494
|
+
if (typeConfig.fieldType === "boolean" || isCheckboxType(typeConfig.fieldType)) {
|
|
8495
|
+
return html`
|
|
8496
|
+
<input
|
|
8497
|
+
type="checkbox"
|
|
8498
|
+
class="${fieldClass}"
|
|
8499
|
+
.checked="${currentValue === "true"}"
|
|
8500
|
+
title="${requiredTitle}"
|
|
8501
|
+
@change="${onChange}"
|
|
8502
|
+
@blur="${onBlur}"
|
|
8503
|
+
/>
|
|
8504
|
+
`;
|
|
8505
|
+
}
|
|
8506
|
+
const inputType = typeConfig.htmlInputType || "text";
|
|
8507
|
+
const placeholder = `${typeConfig.placeholder || I18n.t("section.enterPlaceholder")} ${typedMember.axisLabel}`;
|
|
8508
|
+
return html`
|
|
8509
|
+
<input
|
|
8510
|
+
type="${inputType}"
|
|
8511
|
+
inputmode="${typeConfig.inputMode || ""}"
|
|
8512
|
+
class="${fieldClass}"
|
|
8513
|
+
placeholder="${placeholder}"
|
|
8514
|
+
.value="${currentValue}"
|
|
8515
|
+
min="${typeConfig.min !== void 0 ? typeConfig.min : ""}"
|
|
8516
|
+
max="${typeConfig.max !== void 0 ? typeConfig.max : ""}"
|
|
8517
|
+
step="${typeConfig.step !== void 0 ? typeConfig.step : ""}"
|
|
8518
|
+
title="${requiredTitle}"
|
|
8519
|
+
@input="${onChange}"
|
|
8520
|
+
@blur="${onBlur}"
|
|
8521
|
+
/>
|
|
8522
|
+
`;
|
|
8523
|
+
}
|
|
7904
8524
|
_handleAddRepeat(event) {
|
|
7905
8525
|
event.stopPropagation();
|
|
7906
8526
|
this.dispatchEvent(new CustomEvent("add-concept-repeat", {
|
|
@@ -8044,23 +8664,24 @@ let JupiterFormSection = class extends LitElement {
|
|
|
8044
8664
|
<!-- Typed member input fields in column header -->
|
|
8045
8665
|
${((_a = column2.dimensionData) == null ? void 0 : _a.hasTypedMembers) ? html`
|
|
8046
8666
|
<div class="typed-members-header">
|
|
8047
|
-
${(_c = (_b = column2.dimensionData) == null ? void 0 : _b.typedMembers) == null ? void 0 : _c.map((typedMember) =>
|
|
8667
|
+
${(_c = (_b = column2.dimensionData) == null ? void 0 : _b.typedMembers) == null ? void 0 : _c.map((typedMember) => {
|
|
8668
|
+
const isRequired = !this._getTypedMemberHeaderValue(column2.id, typedMember.axisId) && this._columnHasAnyValue(column2.id);
|
|
8669
|
+
return html`
|
|
8048
8670
|
<div class="typed-member-header-input">
|
|
8049
8671
|
${this.mode === "readonly" ? html`
|
|
8050
8672
|
<span class="typed-member-header-field" style="display:block;padding:4px 0;">
|
|
8051
8673
|
${this._getTypedMemberHeaderValue(column2.id, typedMember.axisId) || "â"}
|
|
8052
8674
|
</span>
|
|
8053
8675
|
` : html`
|
|
8054
|
-
<input
|
|
8055
|
-
|
|
8056
|
-
class="typed-member-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
@input="${(e2) => this._handleTypedMemberHeaderChange(e2, column2.id, typedMember.axisId)}"
|
|
8060
|
-
/>
|
|
8676
|
+
<div class="typed-member-input-wrapper">
|
|
8677
|
+
${this._renderTypedMemberHeaderInput(column2.id, typedMember, isRequired)}
|
|
8678
|
+
${isRequired ? html`<span class="typed-member-required-asterisk" title="${I18n.t("validation.typedMemberRequired")}">*</span>` : ""}
|
|
8679
|
+
</div>
|
|
8680
|
+
${this._renderTypedMemberHeaderErrors(column2.id, typedMember.axisId)}
|
|
8061
8681
|
`}
|
|
8062
8682
|
</div>
|
|
8063
|
-
|
|
8683
|
+
`;
|
|
8684
|
+
})}
|
|
8064
8685
|
</div>
|
|
8065
8686
|
` : ""}
|
|
8066
8687
|
</div>
|
|
@@ -8133,6 +8754,7 @@ let JupiterFormSection = class extends LitElement {
|
|
|
8133
8754
|
.availableDimensions="${this.availableDimensions}"
|
|
8134
8755
|
.periodStartDate="${this.periodStartDate}"
|
|
8135
8756
|
.periodEndDate="${this.periodEndDate}"
|
|
8757
|
+
.datatypes="${this.datatypes}"
|
|
8136
8758
|
@dialog-cancel="${this._handleDialogCancel}"
|
|
8137
8759
|
@column-add="${this._handleColumnAdd}"
|
|
8138
8760
|
></jupiter-add-column-dialog>
|
|
@@ -8386,6 +9008,34 @@ JupiterFormSection.styles = css`
|
|
|
8386
9008
|
gap: 2px;
|
|
8387
9009
|
}
|
|
8388
9010
|
|
|
9011
|
+
.typed-member-input-wrapper {
|
|
9012
|
+
display: flex;
|
|
9013
|
+
align-items: center;
|
|
9014
|
+
gap: 4px;
|
|
9015
|
+
}
|
|
9016
|
+
|
|
9017
|
+
.typed-member-header-field.required-field,
|
|
9018
|
+
.typed-member-header-field.has-error {
|
|
9019
|
+
border-color: var(--jupiter-error-color, #d32f2f);
|
|
9020
|
+
}
|
|
9021
|
+
|
|
9022
|
+
.typed-member-header-errors {
|
|
9023
|
+
margin-top: 2px;
|
|
9024
|
+
}
|
|
9025
|
+
|
|
9026
|
+
.typed-member-header-error {
|
|
9027
|
+
display: block;
|
|
9028
|
+
font-size: 11px;
|
|
9029
|
+
color: var(--jupiter-error-color, #d32f2f);
|
|
9030
|
+
}
|
|
9031
|
+
|
|
9032
|
+
.typed-member-required-asterisk {
|
|
9033
|
+
color: var(--jupiter-error-color, #d32f2f);
|
|
9034
|
+
font-weight: bold;
|
|
9035
|
+
font-size: 13px;
|
|
9036
|
+
flex-shrink: 0;
|
|
9037
|
+
}
|
|
9038
|
+
|
|
8389
9039
|
.typed-member-header-label {
|
|
8390
9040
|
font-size: 11px;
|
|
8391
9041
|
font-weight: 500;
|
|
@@ -8514,6 +9164,9 @@ __decorateClass$4([
|
|
|
8514
9164
|
__decorateClass$4([
|
|
8515
9165
|
r()
|
|
8516
9166
|
], JupiterFormSection.prototype, "_insertAfterColumnId", 2);
|
|
9167
|
+
__decorateClass$4([
|
|
9168
|
+
r()
|
|
9169
|
+
], JupiterFormSection.prototype, "_typedMemberErrors", 2);
|
|
8517
9170
|
__decorateClass$4([
|
|
8518
9171
|
r()
|
|
8519
9172
|
], JupiterFormSection.prototype, "_expandedConcepts", 2);
|
|
@@ -10919,6 +11572,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
10919
11572
|
this.readonly = false;
|
|
10920
11573
|
this.calculationEnabled = false;
|
|
10921
11574
|
this.formulaEnabled = false;
|
|
11575
|
+
this.tableLayoutEnabled = false;
|
|
10922
11576
|
this.periodStartDate = "2025-01-01";
|
|
10923
11577
|
this.periodEndDate = "2025-12-31";
|
|
10924
11578
|
this.language = "en";
|
|
@@ -10928,6 +11582,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
10928
11582
|
this.roleFilterAxes = [];
|
|
10929
11583
|
this.showLastValidationResultBtn = false;
|
|
10930
11584
|
this.isDraftSaving = false;
|
|
11585
|
+
this.hasUnsavedChanges = false;
|
|
10931
11586
|
this.defaultUnits = [];
|
|
10932
11587
|
this.decimals = "INF";
|
|
10933
11588
|
this._formData = {};
|
|
@@ -10978,6 +11633,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
10978
11633
|
this._skipPeriodPreferencesRestore = false;
|
|
10979
11634
|
this._autoSaveTimer = null;
|
|
10980
11635
|
this._lastAutoSaveSnapshot = "";
|
|
11636
|
+
this._autoSaveBaselineSeeded = false;
|
|
10981
11637
|
this._lastSavedDraftSnapshot = "";
|
|
10982
11638
|
}
|
|
10983
11639
|
connectedCallback() {
|
|
@@ -11016,6 +11672,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
11016
11672
|
const oldValue = this._writeFormDataValue(conceptId, columnId, value);
|
|
11017
11673
|
this._touched.add(`${conceptId}-${columnId}`);
|
|
11018
11674
|
this._dirty = true;
|
|
11675
|
+
this.hasUnsavedChanges = true;
|
|
11019
11676
|
this._validateForm();
|
|
11020
11677
|
this.requestUpdate();
|
|
11021
11678
|
this.dispatchEvent(new CustomEvent("field-change", {
|
|
@@ -11031,9 +11688,10 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
11031
11688
|
});
|
|
11032
11689
|
this._autoSaveTimer = setInterval(() => {
|
|
11033
11690
|
const currentSnapshot = JSON.stringify(this._formData);
|
|
11034
|
-
if (this.
|
|
11691
|
+
if (!this._autoSaveBaselineSeeded) {
|
|
11035
11692
|
this._lastAutoSaveSnapshot = currentSnapshot;
|
|
11036
|
-
|
|
11693
|
+
this._autoSaveBaselineSeeded = true;
|
|
11694
|
+
console.log("âšī¸ [Auto Save] Baseline initialized on first tick (fallback); no draft save emitted.");
|
|
11037
11695
|
return;
|
|
11038
11696
|
}
|
|
11039
11697
|
if (currentSnapshot !== this._lastAutoSaveSnapshot) {
|
|
@@ -11052,7 +11710,27 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
11052
11710
|
this._autoSaveTimer = null;
|
|
11053
11711
|
}
|
|
11054
11712
|
}
|
|
11713
|
+
/**
|
|
11714
|
+
* Capture the current _formData as the auto-save baseline.
|
|
11715
|
+
*
|
|
11716
|
+
* Must be called once the form has finished populating _formData on load
|
|
11717
|
+
* (after _loadDraftIfExists() resolves). Seeding here â rather than lazily on
|
|
11718
|
+
* the first 30s timer tick â ensures edits made within the first 30 seconds are
|
|
11719
|
+
* detected as changes and auto-saved, instead of being silently folded into the
|
|
11720
|
+
* baseline and never persisted.
|
|
11721
|
+
*/
|
|
11722
|
+
_seedAutoSaveBaseline() {
|
|
11723
|
+
this._lastAutoSaveSnapshot = JSON.stringify(this._formData);
|
|
11724
|
+
this._autoSaveBaselineSeeded = true;
|
|
11725
|
+
console.log("âšī¸ [Auto Save] Baseline seeded from loaded form state.");
|
|
11726
|
+
}
|
|
11055
11727
|
updated(changedProperties) {
|
|
11728
|
+
if (changedProperties.has("hasUnsavedChanges")) {
|
|
11729
|
+
this.dispatchEvent(new CustomEvent("has-unsaved-changes-change", {
|
|
11730
|
+
detail: { hasUnsavedChanges: this.hasUnsavedChanges },
|
|
11731
|
+
bubbles: true
|
|
11732
|
+
}));
|
|
11733
|
+
}
|
|
11056
11734
|
if (changedProperties.has("decimals") || changedProperties.has("masterData")) {
|
|
11057
11735
|
this._computeEffectiveMasterData();
|
|
11058
11736
|
}
|
|
@@ -11178,12 +11856,15 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
11178
11856
|
this._extractTypedMembersFromFacts();
|
|
11179
11857
|
if (!this._skipDraftLoading) {
|
|
11180
11858
|
this._loadDraftIfExists().then(() => {
|
|
11859
|
+
this._seedAutoSaveBaseline();
|
|
11181
11860
|
this._analyzeAllVisibleRoles();
|
|
11182
11861
|
}).catch(() => {
|
|
11862
|
+
this._seedAutoSaveBaseline();
|
|
11183
11863
|
this._analyzeAllVisibleRoles();
|
|
11184
11864
|
});
|
|
11185
11865
|
} else {
|
|
11186
11866
|
console.log("âī¸ Skipping draft loading during preference update");
|
|
11867
|
+
this._seedAutoSaveBaseline();
|
|
11187
11868
|
this._analyzeAllVisibleRoles();
|
|
11188
11869
|
}
|
|
11189
11870
|
this._validateForm();
|
|
@@ -11457,22 +12138,34 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
11457
12138
|
*
|
|
11458
12139
|
* Skipped when form state is being restored from external props or localStorage so that
|
|
11459
12140
|
* the user's previously saved preferences are not overridden.
|
|
12141
|
+
*
|
|
12142
|
+
* Roles that explicitly set showPreviousYear: false in presentation.json opt out of this
|
|
12143
|
+
* auto-enable â that's a deliberate taxonomy-author decision and must win over the
|
|
12144
|
+
* dimension-member heuristic below.
|
|
11460
12145
|
*/
|
|
11461
12146
|
_applyDimensionShowPreviousYear() {
|
|
11462
|
-
var _a, _b;
|
|
12147
|
+
var _a, _b, _c, _d, _e, _f;
|
|
11463
12148
|
if (this.mode !== "admin")
|
|
11464
12149
|
return;
|
|
11465
12150
|
const hasExternalState = !!(this.dynaformsFacts && this.dynaformsMetadata);
|
|
11466
12151
|
const hasLocalDraft = ((_a = this._draftStorageService) == null ? void 0 : _a.hasDraft()) ?? false;
|
|
11467
12152
|
if (hasExternalState || hasLocalDraft)
|
|
11468
12153
|
return;
|
|
12154
|
+
const explicitlyDisabled = /* @__PURE__ */ new Set();
|
|
12155
|
+
(_e = (_d = (_c = (_b = this.xbrlInput) == null ? void 0 : _b.presentation) == null ? void 0 : _c[0]) == null ? void 0 : _d.roles) == null ? void 0 : _e.forEach((role) => {
|
|
12156
|
+
if (role.showPreviousYear === false) {
|
|
12157
|
+
explicitlyDisabled.add(role.id);
|
|
12158
|
+
}
|
|
12159
|
+
});
|
|
11469
12160
|
const updated = { ...this._periodPreferences };
|
|
11470
12161
|
let changed = false;
|
|
11471
12162
|
for (const roleId of Object.keys(updated)) {
|
|
12163
|
+
if (explicitlyDisabled.has(roleId))
|
|
12164
|
+
continue;
|
|
11472
12165
|
const prefs = updated[roleId];
|
|
11473
12166
|
if (prefs.showPreviousYear)
|
|
11474
12167
|
continue;
|
|
11475
|
-
const hasDimensionMembers = ((
|
|
12168
|
+
const hasDimensionMembers = ((_f = prefs.dimensionSelections) == null ? void 0 : _f.some(
|
|
11476
12169
|
(ds) => ds.selectedMemberIds && ds.selectedMemberIds.length > 0
|
|
11477
12170
|
)) ?? false;
|
|
11478
12171
|
if (hasDimensionMembers) {
|
|
@@ -11755,6 +12448,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
11755
12448
|
console.log("đ Section IDs:", (_b = this._currentSchema) == null ? void 0 : _b.sections.map((s2) => s2.id));
|
|
11756
12449
|
console.log("đĨ Restoring form data from draft after reinitialization...");
|
|
11757
12450
|
this._loadDraftIfExists().then(() => {
|
|
12451
|
+
this._seedAutoSaveBaseline();
|
|
11758
12452
|
console.log("â
Form data restored successfully after reinitialization");
|
|
11759
12453
|
}).catch((error2) => {
|
|
11760
12454
|
console.error("â Error restoring form data after reinitialization:", error2);
|
|
@@ -11871,6 +12565,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
11871
12565
|
console.log(`đž Updated formData[${conceptId}]:`, this._formData[conceptId]);
|
|
11872
12566
|
this._touched.add(`${conceptId}-${columnId}`);
|
|
11873
12567
|
this._dirty = true;
|
|
12568
|
+
this.hasUnsavedChanges = true;
|
|
11874
12569
|
this._validateForm();
|
|
11875
12570
|
this.requestUpdate();
|
|
11876
12571
|
this.dispatchEvent(new CustomEvent("field-change", {
|
|
@@ -11902,6 +12597,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
11902
12597
|
this._storeUnit(conceptId, columnId, unit);
|
|
11903
12598
|
}
|
|
11904
12599
|
this._dirty = true;
|
|
12600
|
+
this.hasUnsavedChanges = true;
|
|
11905
12601
|
this.requestUpdate();
|
|
11906
12602
|
}
|
|
11907
12603
|
_handleUnitChange(event) {
|
|
@@ -11911,6 +12607,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
11911
12607
|
console.log(`đˇī¸ Unit change received: conceptId=${conceptId}, columnId=${columnId}, unit=${unit}`);
|
|
11912
12608
|
this._storeUnit(conceptId, columnId, unit);
|
|
11913
12609
|
this._dirty = true;
|
|
12610
|
+
this.hasUnsavedChanges = true;
|
|
11914
12611
|
this.requestUpdate();
|
|
11915
12612
|
}
|
|
11916
12613
|
_storeUnit(conceptId, columnId, unit) {
|
|
@@ -11935,6 +12632,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
11935
12632
|
return;
|
|
11936
12633
|
this._storeDecimals(conceptId, columnId, decimals);
|
|
11937
12634
|
this._dirty = true;
|
|
12635
|
+
this.hasUnsavedChanges = true;
|
|
11938
12636
|
this.requestUpdate();
|
|
11939
12637
|
}
|
|
11940
12638
|
_computeEffectiveMasterData() {
|
|
@@ -12076,6 +12774,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
12076
12774
|
const { conceptId } = event.detail;
|
|
12077
12775
|
this._repeatCounts = { ...this._repeatCounts, [conceptId]: (this._repeatCounts[conceptId] || 0) + 1 };
|
|
12078
12776
|
this._dirty = true;
|
|
12777
|
+
this.hasUnsavedChanges = true;
|
|
12079
12778
|
}
|
|
12080
12779
|
_handleRemoveConceptRepeat(event) {
|
|
12081
12780
|
const { conceptId: rawConceptId } = event.detail;
|
|
@@ -12128,6 +12827,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
12128
12827
|
this._periodData = newPeriodData;
|
|
12129
12828
|
this._repeatCounts = { ...this._repeatCounts, [baseConceptId]: current - 1 };
|
|
12130
12829
|
this._dirty = true;
|
|
12830
|
+
this.hasUnsavedChanges = true;
|
|
12131
12831
|
}
|
|
12132
12832
|
_handleTypedMemberChange(event) {
|
|
12133
12833
|
const { columnId, axisId, value, conceptId } = event.detail;
|
|
@@ -12140,6 +12840,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
12140
12840
|
updatedTypedMemberData[storageKey] = { ...updatedTypedMemberData[storageKey], [axisId]: value };
|
|
12141
12841
|
this._typedMemberData = updatedTypedMemberData;
|
|
12142
12842
|
this._dirty = true;
|
|
12843
|
+
this.hasUnsavedChanges = true;
|
|
12143
12844
|
this.requestUpdate();
|
|
12144
12845
|
this.dispatchEvent(new CustomEvent("typed-member-change", {
|
|
12145
12846
|
detail: { columnId, axisId, value },
|
|
@@ -12338,6 +13039,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
12338
13039
|
}
|
|
12339
13040
|
this._removeColumnData(columnId, sectionId);
|
|
12340
13041
|
this._dirty = true;
|
|
13042
|
+
this.hasUnsavedChanges = true;
|
|
12341
13043
|
this._validateForm();
|
|
12342
13044
|
this.requestUpdate();
|
|
12343
13045
|
this.dispatchEvent(new CustomEvent("column-remove", {
|
|
@@ -12354,6 +13056,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
12354
13056
|
}
|
|
12355
13057
|
this._hiddenColumnIds.get(sectionId).add(columnId);
|
|
12356
13058
|
this._dirty = true;
|
|
13059
|
+
this.hasUnsavedChanges = true;
|
|
12357
13060
|
this.requestUpdate();
|
|
12358
13061
|
this.dispatchEvent(new CustomEvent("column-hide", {
|
|
12359
13062
|
detail: { columnId, sectionId },
|
|
@@ -12367,6 +13070,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
12367
13070
|
return;
|
|
12368
13071
|
(_a = this._hiddenColumnIds.get(sectionId)) == null ? void 0 : _a.delete(columnId);
|
|
12369
13072
|
this._dirty = true;
|
|
13073
|
+
this.hasUnsavedChanges = true;
|
|
12370
13074
|
this.requestUpdate();
|
|
12371
13075
|
this.dispatchEvent(new CustomEvent("column-show", {
|
|
12372
13076
|
detail: { columnId, sectionId },
|
|
@@ -12430,8 +13134,8 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
12430
13134
|
if (dimension.typedMember) {
|
|
12431
13135
|
availableDimension.typedMember = {
|
|
12432
13136
|
id: dimension.typedMember.id,
|
|
12433
|
-
dataType: "string"
|
|
12434
|
-
|
|
13137
|
+
dataType: dimension.typedMember.type || "string",
|
|
13138
|
+
validation: dimension.typedMember.validation
|
|
12435
13139
|
};
|
|
12436
13140
|
}
|
|
12437
13141
|
availableDimensions.push(availableDimension);
|
|
@@ -12652,8 +13356,10 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
12652
13356
|
axisLabel: dim.axisLabel,
|
|
12653
13357
|
typedMemberId: dim.axisId,
|
|
12654
13358
|
// Use axisId as typedMemberId for typed dimensions
|
|
12655
|
-
memberLabel: dim.axisLabel
|
|
13359
|
+
memberLabel: dim.axisLabel,
|
|
12656
13360
|
// Use axisLabel as memberLabel
|
|
13361
|
+
type: dim.dataType,
|
|
13362
|
+
validation: dim.validation
|
|
12657
13363
|
}));
|
|
12658
13364
|
}
|
|
12659
13365
|
}
|
|
@@ -12682,6 +13388,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
12682
13388
|
}
|
|
12683
13389
|
this._replicateFieldsForNewColumn(newColumnId, request, sectionId);
|
|
12684
13390
|
this._dirty = true;
|
|
13391
|
+
this.hasUnsavedChanges = true;
|
|
12685
13392
|
this.requestUpdate();
|
|
12686
13393
|
this.dispatchEvent(new CustomEvent("column-add", {
|
|
12687
13394
|
detail: { column: newColumn },
|
|
@@ -13001,9 +13708,11 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
13001
13708
|
});
|
|
13002
13709
|
if (draftPayloadSnapshot === this._lastSavedDraftSnapshot) {
|
|
13003
13710
|
console.log(`âšī¸ [Save Draft] No draft content change detected (${source}); skipping event emit.`);
|
|
13711
|
+
this.hasUnsavedChanges = false;
|
|
13004
13712
|
return;
|
|
13005
13713
|
}
|
|
13006
13714
|
this._lastSavedDraftSnapshot = draftPayloadSnapshot;
|
|
13715
|
+
this.hasUnsavedChanges = false;
|
|
13007
13716
|
const saved = this._draftStorageService.saveDraft(draftData, metadata);
|
|
13008
13717
|
console.log("đž [Save Draft] Metadata saved with enhanced selectedRoleIds:", metadata.selectedRoleIds);
|
|
13009
13718
|
this.dynaformsFacts = draftData;
|
|
@@ -14087,6 +14796,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
14087
14796
|
this._formData = { ...this.initialData };
|
|
14088
14797
|
this._touched.clear();
|
|
14089
14798
|
this._dirty = false;
|
|
14799
|
+
this.hasUnsavedChanges = false;
|
|
14090
14800
|
this._submitted = false;
|
|
14091
14801
|
this._preservedFormData = {};
|
|
14092
14802
|
this._preservedTypedMemberData = {};
|
|
@@ -15030,6 +15740,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
15030
15740
|
setData(data) {
|
|
15031
15741
|
this._formData = { ...data };
|
|
15032
15742
|
this._dirty = true;
|
|
15743
|
+
this.hasUnsavedChanges = true;
|
|
15033
15744
|
this._validateForm();
|
|
15034
15745
|
this.requestUpdate();
|
|
15035
15746
|
}
|
|
@@ -15458,7 +16169,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
15458
16169
|
<button
|
|
15459
16170
|
class="btn-secondary"
|
|
15460
16171
|
@click="${this._handleSaveDraft}"
|
|
15461
|
-
?disabled="${this.disabled || this.readonly || this.isDraftSaving}"
|
|
16172
|
+
?disabled="${this.disabled || this.readonly || this.isDraftSaving || !this.hasUnsavedChanges}"
|
|
15462
16173
|
>
|
|
15463
16174
|
${this.isDraftSaving ? I18n.t("form.savingDraft") : I18n.t("form.saveDraft")}
|
|
15464
16175
|
</button>
|
|
@@ -16456,6 +17167,9 @@ __decorateClass([
|
|
|
16456
17167
|
__decorateClass([
|
|
16457
17168
|
n2({ type: Boolean, attribute: "formula-enabled" })
|
|
16458
17169
|
], JupiterDynamicForm.prototype, "formulaEnabled", 2);
|
|
17170
|
+
__decorateClass([
|
|
17171
|
+
n2({ type: Boolean, attribute: "table-layout-enabled" })
|
|
17172
|
+
], JupiterDynamicForm.prototype, "tableLayoutEnabled", 2);
|
|
16459
17173
|
__decorateClass([
|
|
16460
17174
|
n2({ type: String })
|
|
16461
17175
|
], JupiterDynamicForm.prototype, "periodStartDate", 2);
|
|
@@ -16486,6 +17200,9 @@ __decorateClass([
|
|
|
16486
17200
|
__decorateClass([
|
|
16487
17201
|
n2({ type: Boolean, attribute: "is-draft-saving" })
|
|
16488
17202
|
], JupiterDynamicForm.prototype, "isDraftSaving", 2);
|
|
17203
|
+
__decorateClass([
|
|
17204
|
+
n2({ type: Boolean, attribute: "has-unsaved-changes", reflect: true })
|
|
17205
|
+
], JupiterDynamicForm.prototype, "hasUnsavedChanges", 2);
|
|
16489
17206
|
__decorateClass([
|
|
16490
17207
|
n2({ type: Array })
|
|
16491
17208
|
], JupiterDynamicForm.prototype, "defaultUnits", 2);
|