jupiter-dynamic-forms 1.20.6 → 1.20.7

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/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 indexMemberTree(member, memberQNameToId, memberChildrenByQName) {
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.children || []).forEach((child) => indexMemberTree(child, memberQNameToId, memberChildrenByQName));
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((member) => indexMemberTree(member, memberQNameToId, memberChildrenByQName));
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
- if (memberQName && filter2.attributes["member.axis"] === "child") {
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 resolveFormulaVariable(variable, ctx) {
3108
- const matched = narrowByPreferredOccurrence(
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
- const bindings = /* @__PURE__ */ new Map();
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
- if (assertion.type === "VALUE_ASSERTION") {
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) {
4202
4483
  return {
4203
- fieldType: "currency",
4204
- htmlInputType: "text",
4205
- inputMode: "decimal",
4206
- allowDecimals: true,
4207
- step: 0.01,
4208
- placeholder: I18n.t("field.enterCurrency")
4484
+ type: "maxLength",
4485
+ message: I18n.t("xbrlValidation.maxLength", { required: maxLength, current: value.length }),
4486
+ expectedValue: maxLength,
4487
+ actualValue: value.length
4209
4488
  };
4210
4489
  }
4211
- return {
4212
- fieldType: "decimal",
4213
- htmlInputType: "text",
4214
- inputMode: "decimal",
4215
- allowDecimals: true,
4216
- step: "any",
4217
- placeholder: I18n.t("field.enterDecimalValue")
4218
- };
4490
+ return null;
4219
4491
  }
4220
- if (baseTypeChain.some((type) => type === "xbrli:stringItemType") || chainLower.some((type) => type === "string" || type.includes("stringitem"))) {
4221
- return {
4222
- fieldType: "text",
4223
- htmlInputType: "text",
4224
- placeholder: I18n.t("field.enterText")
4225
- };
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) {
4498
+ return {
4499
+ type: "totalDigits",
4500
+ message: I18n.t("xbrlValidation.totalDigits", { required: totalDigits, current: digitsOnly.length }),
4501
+ expectedValue: totalDigits,
4502
+ actualValue: digitsOnly.length
4503
+ };
4504
+ }
4505
+ return null;
4506
+ }
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 input = event.target;
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 = input.value;
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
- <input
6834
- type="text"
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) => html`
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
- type="text"
8056
- class="typed-member-header-field"
8057
- placeholder="${I18n.t("section.enterPlaceholder")} ${typedMember.axisLabel}"
8058
- .value="${this._getTypedMemberHeaderValue(column2.id, typedMember.axisId)}"
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 = {};
@@ -11016,6 +11671,7 @@ let JupiterDynamicForm = class extends LitElement {
11016
11671
  const oldValue = this._writeFormDataValue(conceptId, columnId, value);
11017
11672
  this._touched.add(`${conceptId}-${columnId}`);
11018
11673
  this._dirty = true;
11674
+ this.hasUnsavedChanges = true;
11019
11675
  this._validateForm();
11020
11676
  this.requestUpdate();
11021
11677
  this.dispatchEvent(new CustomEvent("field-change", {
@@ -11053,6 +11709,12 @@ let JupiterDynamicForm = class extends LitElement {
11053
11709
  }
11054
11710
  }
11055
11711
  updated(changedProperties) {
11712
+ if (changedProperties.has("hasUnsavedChanges")) {
11713
+ this.dispatchEvent(new CustomEvent("has-unsaved-changes-change", {
11714
+ detail: { hasUnsavedChanges: this.hasUnsavedChanges },
11715
+ bubbles: true
11716
+ }));
11717
+ }
11056
11718
  if (changedProperties.has("decimals") || changedProperties.has("masterData")) {
11057
11719
  this._computeEffectiveMasterData();
11058
11720
  }
@@ -11457,22 +12119,34 @@ let JupiterDynamicForm = class extends LitElement {
11457
12119
  *
11458
12120
  * Skipped when form state is being restored from external props or localStorage so that
11459
12121
  * the user's previously saved preferences are not overridden.
12122
+ *
12123
+ * Roles that explicitly set showPreviousYear: false in presentation.json opt out of this
12124
+ * auto-enable — that's a deliberate taxonomy-author decision and must win over the
12125
+ * dimension-member heuristic below.
11460
12126
  */
11461
12127
  _applyDimensionShowPreviousYear() {
11462
- var _a, _b;
12128
+ var _a, _b, _c, _d, _e, _f;
11463
12129
  if (this.mode !== "admin")
11464
12130
  return;
11465
12131
  const hasExternalState = !!(this.dynaformsFacts && this.dynaformsMetadata);
11466
12132
  const hasLocalDraft = ((_a = this._draftStorageService) == null ? void 0 : _a.hasDraft()) ?? false;
11467
12133
  if (hasExternalState || hasLocalDraft)
11468
12134
  return;
12135
+ const explicitlyDisabled = /* @__PURE__ */ new Set();
12136
+ (_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) => {
12137
+ if (role.showPreviousYear === false) {
12138
+ explicitlyDisabled.add(role.id);
12139
+ }
12140
+ });
11469
12141
  const updated = { ...this._periodPreferences };
11470
12142
  let changed = false;
11471
12143
  for (const roleId of Object.keys(updated)) {
12144
+ if (explicitlyDisabled.has(roleId))
12145
+ continue;
11472
12146
  const prefs = updated[roleId];
11473
12147
  if (prefs.showPreviousYear)
11474
12148
  continue;
11475
- const hasDimensionMembers = ((_b = prefs.dimensionSelections) == null ? void 0 : _b.some(
12149
+ const hasDimensionMembers = ((_f = prefs.dimensionSelections) == null ? void 0 : _f.some(
11476
12150
  (ds) => ds.selectedMemberIds && ds.selectedMemberIds.length > 0
11477
12151
  )) ?? false;
11478
12152
  if (hasDimensionMembers) {
@@ -11871,6 +12545,7 @@ let JupiterDynamicForm = class extends LitElement {
11871
12545
  console.log(`💾 Updated formData[${conceptId}]:`, this._formData[conceptId]);
11872
12546
  this._touched.add(`${conceptId}-${columnId}`);
11873
12547
  this._dirty = true;
12548
+ this.hasUnsavedChanges = true;
11874
12549
  this._validateForm();
11875
12550
  this.requestUpdate();
11876
12551
  this.dispatchEvent(new CustomEvent("field-change", {
@@ -11902,6 +12577,7 @@ let JupiterDynamicForm = class extends LitElement {
11902
12577
  this._storeUnit(conceptId, columnId, unit);
11903
12578
  }
11904
12579
  this._dirty = true;
12580
+ this.hasUnsavedChanges = true;
11905
12581
  this.requestUpdate();
11906
12582
  }
11907
12583
  _handleUnitChange(event) {
@@ -11911,6 +12587,7 @@ let JupiterDynamicForm = class extends LitElement {
11911
12587
  console.log(`đŸˇī¸ Unit change received: conceptId=${conceptId}, columnId=${columnId}, unit=${unit}`);
11912
12588
  this._storeUnit(conceptId, columnId, unit);
11913
12589
  this._dirty = true;
12590
+ this.hasUnsavedChanges = true;
11914
12591
  this.requestUpdate();
11915
12592
  }
11916
12593
  _storeUnit(conceptId, columnId, unit) {
@@ -11935,6 +12612,7 @@ let JupiterDynamicForm = class extends LitElement {
11935
12612
  return;
11936
12613
  this._storeDecimals(conceptId, columnId, decimals);
11937
12614
  this._dirty = true;
12615
+ this.hasUnsavedChanges = true;
11938
12616
  this.requestUpdate();
11939
12617
  }
11940
12618
  _computeEffectiveMasterData() {
@@ -12076,6 +12754,7 @@ let JupiterDynamicForm = class extends LitElement {
12076
12754
  const { conceptId } = event.detail;
12077
12755
  this._repeatCounts = { ...this._repeatCounts, [conceptId]: (this._repeatCounts[conceptId] || 0) + 1 };
12078
12756
  this._dirty = true;
12757
+ this.hasUnsavedChanges = true;
12079
12758
  }
12080
12759
  _handleRemoveConceptRepeat(event) {
12081
12760
  const { conceptId: rawConceptId } = event.detail;
@@ -12128,6 +12807,7 @@ let JupiterDynamicForm = class extends LitElement {
12128
12807
  this._periodData = newPeriodData;
12129
12808
  this._repeatCounts = { ...this._repeatCounts, [baseConceptId]: current - 1 };
12130
12809
  this._dirty = true;
12810
+ this.hasUnsavedChanges = true;
12131
12811
  }
12132
12812
  _handleTypedMemberChange(event) {
12133
12813
  const { columnId, axisId, value, conceptId } = event.detail;
@@ -12140,6 +12820,7 @@ let JupiterDynamicForm = class extends LitElement {
12140
12820
  updatedTypedMemberData[storageKey] = { ...updatedTypedMemberData[storageKey], [axisId]: value };
12141
12821
  this._typedMemberData = updatedTypedMemberData;
12142
12822
  this._dirty = true;
12823
+ this.hasUnsavedChanges = true;
12143
12824
  this.requestUpdate();
12144
12825
  this.dispatchEvent(new CustomEvent("typed-member-change", {
12145
12826
  detail: { columnId, axisId, value },
@@ -12338,6 +13019,7 @@ let JupiterDynamicForm = class extends LitElement {
12338
13019
  }
12339
13020
  this._removeColumnData(columnId, sectionId);
12340
13021
  this._dirty = true;
13022
+ this.hasUnsavedChanges = true;
12341
13023
  this._validateForm();
12342
13024
  this.requestUpdate();
12343
13025
  this.dispatchEvent(new CustomEvent("column-remove", {
@@ -12354,6 +13036,7 @@ let JupiterDynamicForm = class extends LitElement {
12354
13036
  }
12355
13037
  this._hiddenColumnIds.get(sectionId).add(columnId);
12356
13038
  this._dirty = true;
13039
+ this.hasUnsavedChanges = true;
12357
13040
  this.requestUpdate();
12358
13041
  this.dispatchEvent(new CustomEvent("column-hide", {
12359
13042
  detail: { columnId, sectionId },
@@ -12367,6 +13050,7 @@ let JupiterDynamicForm = class extends LitElement {
12367
13050
  return;
12368
13051
  (_a = this._hiddenColumnIds.get(sectionId)) == null ? void 0 : _a.delete(columnId);
12369
13052
  this._dirty = true;
13053
+ this.hasUnsavedChanges = true;
12370
13054
  this.requestUpdate();
12371
13055
  this.dispatchEvent(new CustomEvent("column-show", {
12372
13056
  detail: { columnId, sectionId },
@@ -12430,8 +13114,8 @@ let JupiterDynamicForm = class extends LitElement {
12430
13114
  if (dimension.typedMember) {
12431
13115
  availableDimension.typedMember = {
12432
13116
  id: dimension.typedMember.id,
12433
- dataType: "string"
12434
- // Default to string for now
13117
+ dataType: dimension.typedMember.type || "string",
13118
+ validation: dimension.typedMember.validation
12435
13119
  };
12436
13120
  }
12437
13121
  availableDimensions.push(availableDimension);
@@ -12652,8 +13336,10 @@ let JupiterDynamicForm = class extends LitElement {
12652
13336
  axisLabel: dim.axisLabel,
12653
13337
  typedMemberId: dim.axisId,
12654
13338
  // Use axisId as typedMemberId for typed dimensions
12655
- memberLabel: dim.axisLabel
13339
+ memberLabel: dim.axisLabel,
12656
13340
  // Use axisLabel as memberLabel
13341
+ type: dim.dataType,
13342
+ validation: dim.validation
12657
13343
  }));
12658
13344
  }
12659
13345
  }
@@ -12682,6 +13368,7 @@ let JupiterDynamicForm = class extends LitElement {
12682
13368
  }
12683
13369
  this._replicateFieldsForNewColumn(newColumnId, request, sectionId);
12684
13370
  this._dirty = true;
13371
+ this.hasUnsavedChanges = true;
12685
13372
  this.requestUpdate();
12686
13373
  this.dispatchEvent(new CustomEvent("column-add", {
12687
13374
  detail: { column: newColumn },
@@ -13001,9 +13688,11 @@ let JupiterDynamicForm = class extends LitElement {
13001
13688
  });
13002
13689
  if (draftPayloadSnapshot === this._lastSavedDraftSnapshot) {
13003
13690
  console.log(`â„šī¸ [Save Draft] No draft content change detected (${source}); skipping event emit.`);
13691
+ this.hasUnsavedChanges = false;
13004
13692
  return;
13005
13693
  }
13006
13694
  this._lastSavedDraftSnapshot = draftPayloadSnapshot;
13695
+ this.hasUnsavedChanges = false;
13007
13696
  const saved = this._draftStorageService.saveDraft(draftData, metadata);
13008
13697
  console.log("💾 [Save Draft] Metadata saved with enhanced selectedRoleIds:", metadata.selectedRoleIds);
13009
13698
  this.dynaformsFacts = draftData;
@@ -14087,6 +14776,7 @@ let JupiterDynamicForm = class extends LitElement {
14087
14776
  this._formData = { ...this.initialData };
14088
14777
  this._touched.clear();
14089
14778
  this._dirty = false;
14779
+ this.hasUnsavedChanges = false;
14090
14780
  this._submitted = false;
14091
14781
  this._preservedFormData = {};
14092
14782
  this._preservedTypedMemberData = {};
@@ -15030,6 +15720,7 @@ let JupiterDynamicForm = class extends LitElement {
15030
15720
  setData(data) {
15031
15721
  this._formData = { ...data };
15032
15722
  this._dirty = true;
15723
+ this.hasUnsavedChanges = true;
15033
15724
  this._validateForm();
15034
15725
  this.requestUpdate();
15035
15726
  }
@@ -15458,7 +16149,7 @@ let JupiterDynamicForm = class extends LitElement {
15458
16149
  <button
15459
16150
  class="btn-secondary"
15460
16151
  @click="${this._handleSaveDraft}"
15461
- ?disabled="${this.disabled || this.readonly || this.isDraftSaving}"
16152
+ ?disabled="${this.disabled || this.readonly || this.isDraftSaving || !this.hasUnsavedChanges}"
15462
16153
  >
15463
16154
  ${this.isDraftSaving ? I18n.t("form.savingDraft") : I18n.t("form.saveDraft")}
15464
16155
  </button>
@@ -16456,6 +17147,9 @@ __decorateClass([
16456
17147
  __decorateClass([
16457
17148
  n2({ type: Boolean, attribute: "formula-enabled" })
16458
17149
  ], JupiterDynamicForm.prototype, "formulaEnabled", 2);
17150
+ __decorateClass([
17151
+ n2({ type: Boolean, attribute: "table-layout-enabled" })
17152
+ ], JupiterDynamicForm.prototype, "tableLayoutEnabled", 2);
16459
17153
  __decorateClass([
16460
17154
  n2({ type: String })
16461
17155
  ], JupiterDynamicForm.prototype, "periodStartDate", 2);
@@ -16486,6 +17180,9 @@ __decorateClass([
16486
17180
  __decorateClass([
16487
17181
  n2({ type: Boolean, attribute: "is-draft-saving" })
16488
17182
  ], JupiterDynamicForm.prototype, "isDraftSaving", 2);
17183
+ __decorateClass([
17184
+ n2({ type: Boolean, attribute: "has-unsaved-changes", reflect: true })
17185
+ ], JupiterDynamicForm.prototype, "hasUnsavedChanges", 2);
16489
17186
  __decorateClass([
16490
17187
  n2({ type: Array })
16491
17188
  ], JupiterDynamicForm.prototype, "defaultUnits", 2);