jupiter-dynamic-forms 1.20.5 → 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
  }
@@ -3087,15 +3175,37 @@ function narrowByPreferredOccurrence(candidates, ctx) {
3087
3175
  });
3088
3176
  return result;
3089
3177
  }
3090
- function resolveFormulaVariable(variable, ctx) {
3091
- const matched = narrowByPreferredOccurrence(narrowToCurrentPeriod(resolveMatchedCandidates(variable, ctx), variable, ctx), ctx);
3178
+ function narrowToDirectChildOfDescendantAnchor(candidates, variable, ctx) {
3179
+ var _a, _b;
3180
+ if (variable.bindAsSequence)
3181
+ return candidates;
3182
+ if (new Set(candidates.map((candidate) => candidate.conceptId)).size <= 1)
3183
+ return candidates;
3184
+ const relationFilter = variable.filters.find(
3185
+ (filter2) => filter2.type === "CONCEPT_RELATION" && filter2.attributes["axis"] === "descendant"
3186
+ );
3187
+ const anchorQName = relationFilter == null ? void 0 : relationFilter.attributes["qname"];
3188
+ const linkrole = relationFilter == null ? void 0 : relationFilter.attributes["linkrole"];
3189
+ const anchorConceptId = anchorQName && linkrole ? (_a = ctx.conceptIdByQNamePerRole.get(linkrole)) == null ? void 0 : _a.get(anchorQName) : void 0;
3190
+ const directChildren = new Set(anchorConceptId && linkrole ? ((_b = ctx.childrenByRole.get(linkrole)) == null ? void 0 : _b.get(anchorConceptId)) || [] : []);
3191
+ if (directChildren.size === 0)
3192
+ return candidates;
3193
+ return candidates.filter((candidate) => directChildren.has(candidate.conceptId));
3194
+ }
3195
+ function resolveNarrowedCandidates(variable, ctx) {
3196
+ return narrowByPreferredOccurrence(
3197
+ narrowToDirectChildOfDescendantAnchor(narrowToCurrentPeriod(resolveMatchedCandidates(variable, ctx), variable, ctx), variable, ctx),
3198
+ ctx
3199
+ );
3200
+ }
3201
+ function valuesFromCandidates(variable, matched, ctx) {
3092
3202
  const values = matched.map((candidate) => {
3093
3203
  var _a;
3094
3204
  return resolveRawValue((_a = ctx.formData[candidate.conceptId]) == null ? void 0 : _a[candidate.columnId]);
3095
3205
  }).filter((value) => value !== void 0);
3206
+ if (values.length === 0 && variable.fallbackValue === "()")
3207
+ return [];
3096
3208
  if (variable.bindAsSequence) {
3097
- if (values.length === 0 && variable.fallbackValue === "()")
3098
- return [];
3099
3209
  if (values.length === 0)
3100
3210
  return void 0;
3101
3211
  return values;
@@ -3109,6 +3219,186 @@ function resolveFormulaVariable(variable, ctx) {
3109
3219
  }
3110
3220
  return values[0];
3111
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
+ }
3112
3402
  function resolveFormulaVariableMatchCount(variable, ctx) {
3113
3403
  return resolveMatchedCandidates(variable, ctx).length;
3114
3404
  }
@@ -3151,11 +3441,7 @@ function resolveVariableColumnsForConcept(variable, targetConceptId, ctx) {
3151
3441
  return resolved.map((candidate) => candidate.columnId);
3152
3442
  }
3153
3443
  function buildAssertionBindings(assertion, ctx) {
3154
- const bindings = /* @__PURE__ */ new Map();
3155
- for (const variable of assertion.variables) {
3156
- bindings.set(variable.name, resolveFormulaVariable(variable, ctx));
3157
- }
3158
- return bindings;
3444
+ return resolveAssertionVariableBindings(assertion.variables, ctx, assertion.implicitFiltering);
3159
3445
  }
3160
3446
  function buildAssertionFallbackUsage(assertion, ctx) {
3161
3447
  var _a;
@@ -3248,7 +3534,10 @@ class FormulaValidationService {
3248
3534
  _evaluateAssertion(assertion, role, ctx, language) {
3249
3535
  try {
3250
3536
  const bindings = buildAssertionBindings(assertion, ctx);
3251
- 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) {
3252
3541
  const bindingCheck = checkAllVariablesBound(assertion, bindings);
3253
3542
  if (!bindingCheck.satisfied) {
3254
3543
  return this._skippedResult(assertion, role, bindingCheck.skipReason ?? "unresolvable");
@@ -3264,6 +3553,12 @@ class FormulaValidationService {
3264
3553
  const satisfied2 = evaluateExistenceTest(assertion.test, matchedFactCount);
3265
3554
  return this._evaluatedResult(assertion, role, ctx, satisfied2, language, matchedFactCount);
3266
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
+ }
3267
3562
  const satisfied = evaluateFormulaTest(assertion.test, bindings, fallbackUsage);
3268
3563
  return this._evaluatedResult(assertion, role, ctx, satisfied, language);
3269
3564
  } catch (error2) {
@@ -3592,204 +3887,6 @@ class FactMatcher {
3592
3887
  console.log(" Match Result:", match ? match.value : "No match");
3593
3888
  }
3594
3889
  }
3595
- class XBRLValidator {
3596
- /**
3597
- * Validates a value against XBRL datatype validation rules
3598
- * Only validates when value is not blank
3599
- *
3600
- * @param conceptType - The XBRL concept type (e.g., "nl-types:houseNumberNLItemType")
3601
- * @param value - The input value to validate
3602
- * @param datatypes - Array of XBRL datatypes from taxonomy
3603
- * @returns Validation result with errors if any
3604
- */
3605
- static validateConceptValue(conceptType, value, datatypes) {
3606
- const errors = [];
3607
- if (value === null || value === void 0 || value === "") {
3608
- return { valid: true, errors: [] };
3609
- }
3610
- if (!conceptType || !datatypes || datatypes.length === 0) {
3611
- return { valid: true, errors: [] };
3612
- }
3613
- const datatype = this.findDatatype(conceptType, datatypes);
3614
- if (!datatype || !datatype.validation) {
3615
- return { valid: true, errors: [] };
3616
- }
3617
- const validation2 = datatype.validation;
3618
- const stringValue = String(value);
3619
- if (validation2.pattern) {
3620
- const patternError = this.validatePattern(stringValue, validation2.pattern);
3621
- if (patternError) {
3622
- errors.push(patternError);
3623
- }
3624
- }
3625
- if (validation2.length !== void 0) {
3626
- const lengthError = this.validateExactLength(stringValue, validation2.length);
3627
- if (lengthError) {
3628
- errors.push(lengthError);
3629
- }
3630
- }
3631
- if (validation2.minLength !== void 0) {
3632
- const minLengthError = this.validateMinLength(stringValue, validation2.minLength);
3633
- if (minLengthError) {
3634
- errors.push(minLengthError);
3635
- }
3636
- }
3637
- if (validation2.maxLength !== void 0) {
3638
- const maxLengthError = this.validateMaxLength(stringValue, validation2.maxLength);
3639
- if (maxLengthError) {
3640
- errors.push(maxLengthError);
3641
- }
3642
- }
3643
- if (validation2.totalDigits !== void 0) {
3644
- const totalDigitsError = this.validateTotalDigits(stringValue, validation2.totalDigits);
3645
- if (totalDigitsError) {
3646
- errors.push(totalDigitsError);
3647
- }
3648
- }
3649
- return {
3650
- valid: errors.length === 0,
3651
- errors
3652
- };
3653
- }
3654
- /**
3655
- * Finds the datatype definition for a given concept type
3656
- * Handles both exact matches and base type chain resolution
3657
- */
3658
- static findDatatype(conceptType, datatypes) {
3659
- let datatype = datatypes.find((dt) => dt.type === conceptType);
3660
- if (datatype) {
3661
- return datatype;
3662
- }
3663
- const visited = /* @__PURE__ */ new Set();
3664
- let currentType = conceptType;
3665
- while (currentType && !visited.has(currentType)) {
3666
- visited.add(currentType);
3667
- datatype = datatypes.find((dt) => dt.type === currentType);
3668
- if (datatype) {
3669
- if (datatype.validation) {
3670
- return datatype;
3671
- }
3672
- currentType = datatype.basetype;
3673
- } else {
3674
- break;
3675
- }
3676
- }
3677
- return void 0;
3678
- }
3679
- /**
3680
- * Validates that value matches the regex pattern
3681
- */
3682
- static validatePattern(value, pattern) {
3683
- try {
3684
- const regex = new RegExp(`^${pattern}$`);
3685
- if (!regex.test(value)) {
3686
- return {
3687
- type: "pattern",
3688
- message: I18n.t("xbrlValidation.patternMismatch", { pattern }),
3689
- expectedValue: pattern,
3690
- actualValue: value
3691
- };
3692
- }
3693
- } catch (error2) {
3694
- console.error(`Invalid regex pattern: ${pattern}`, error2);
3695
- return {
3696
- type: "pattern",
3697
- message: I18n.t("xbrlValidation.invalidPattern", { pattern }),
3698
- expectedValue: pattern,
3699
- actualValue: value
3700
- };
3701
- }
3702
- return null;
3703
- }
3704
- /**
3705
- * Validates that value has exact length
3706
- */
3707
- static validateExactLength(value, requiredLength) {
3708
- if (value.length !== requiredLength) {
3709
- return {
3710
- type: "length",
3711
- message: I18n.t("xbrlValidation.exactLength", { required: requiredLength, current: value.length }),
3712
- expectedValue: requiredLength,
3713
- actualValue: value.length
3714
- };
3715
- }
3716
- return null;
3717
- }
3718
- /**
3719
- * Validates minimum length
3720
- */
3721
- static validateMinLength(value, minLength) {
3722
- if (value.length < minLength) {
3723
- return {
3724
- type: "minLength",
3725
- message: I18n.t("xbrlValidation.minLength", { required: minLength, current: value.length }),
3726
- expectedValue: minLength,
3727
- actualValue: value.length
3728
- };
3729
- }
3730
- return null;
3731
- }
3732
- /**
3733
- * Validates maximum length
3734
- */
3735
- static validateMaxLength(value, maxLength) {
3736
- if (value.length > maxLength) {
3737
- return {
3738
- type: "maxLength",
3739
- message: I18n.t("xbrlValidation.maxLength", { required: maxLength, current: value.length }),
3740
- expectedValue: maxLength,
3741
- actualValue: value.length
3742
- };
3743
- }
3744
- return null;
3745
- }
3746
- /**
3747
- * Validates total digits (for numeric values)
3748
- */
3749
- static validateTotalDigits(value, totalDigits) {
3750
- const digitsOnly = value.replace(/[^\d]/g, "");
3751
- if (digitsOnly.length > totalDigits) {
3752
- return {
3753
- type: "totalDigits",
3754
- message: I18n.t("xbrlValidation.totalDigits", { required: totalDigits, current: digitsOnly.length }),
3755
- expectedValue: totalDigits,
3756
- actualValue: digitsOnly.length
3757
- };
3758
- }
3759
- return null;
3760
- }
3761
- /**
3762
- * Gets a human-readable summary of validation rules for a concept type
3763
- * Useful for displaying validation hints to users
3764
- */
3765
- static getValidationRulesSummary(conceptType, datatypes) {
3766
- if (!conceptType || !datatypes) {
3767
- return [];
3768
- }
3769
- const datatype = this.findDatatype(conceptType, datatypes);
3770
- if (!datatype || !datatype.validation) {
3771
- return [];
3772
- }
3773
- const validation2 = datatype.validation;
3774
- const rules = [];
3775
- if (validation2.pattern) {
3776
- rules.push(I18n.t("xbrlValidation.ruleSummaryPattern", { pattern: validation2.pattern }));
3777
- }
3778
- if (validation2.length !== void 0) {
3779
- rules.push(I18n.t("xbrlValidation.ruleSummaryExactLength", { required: validation2.length }));
3780
- }
3781
- if (validation2.minLength !== void 0) {
3782
- rules.push(I18n.t("xbrlValidation.ruleSummaryMinLength", { required: validation2.minLength }));
3783
- }
3784
- if (validation2.maxLength !== void 0) {
3785
- rules.push(I18n.t("xbrlValidation.ruleSummaryMaxLength", { required: validation2.maxLength }));
3786
- }
3787
- if (validation2.totalDigits !== void 0) {
3788
- rules.push(I18n.t("xbrlValidation.ruleSummaryTotalDigits", { required: validation2.totalDigits }));
3789
- }
3790
- return rules;
3791
- }
3792
- }
3793
3890
  const TYPE_INPUT_MAP = {
3794
3891
  // ==========================================
3795
3892
  // Dutch XBRL Types (nl-types namespace)
@@ -3826,14 +3923,21 @@ const TYPE_INPUT_MAP = {
3826
3923
  htmlInputType: "text",
3827
3924
  placeholder: I18n.t("field.enterText")
3828
3925
  },
3829
- // 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.
3830
3930
  "xbrli:dateItemType": {
3831
3931
  fieldType: "date",
3832
- htmlInputType: "date"
3932
+ htmlInputType: "date",
3933
+ min: "1900-01-01",
3934
+ max: "2100-12-31"
3833
3935
  },
3834
3936
  "xbrli:dateTimeItemType": {
3835
3937
  fieldType: "datetime",
3836
- htmlInputType: "datetime-local"
3938
+ htmlInputType: "datetime-local",
3939
+ min: "1900-01-01T00:00",
3940
+ max: "2100-12-31T23:59"
3837
3941
  },
3838
3942
  // Numeric types - integers
3839
3943
  "xbrli:integerItemType": {
@@ -3993,12 +4097,16 @@ function getInputTypeForConceptType(conceptType, datatypes) {
3993
4097
  if (lowerType.includes("time")) {
3994
4098
  return {
3995
4099
  fieldType: "datetime",
3996
- htmlInputType: "datetime-local"
4100
+ htmlInputType: "datetime-local",
4101
+ min: "1900-01-01T00:00",
4102
+ max: "2100-12-31T23:59"
3997
4103
  };
3998
4104
  }
3999
4105
  return {
4000
4106
  fieldType: "date",
4001
- htmlInputType: "date"
4107
+ htmlInputType: "date",
4108
+ min: "1900-01-01",
4109
+ max: "2100-12-31"
4002
4110
  };
4003
4111
  }
4004
4112
  if (lowerType.includes("boolean") || lowerType.includes("bool")) {
@@ -4131,12 +4239,16 @@ function determineInputTypeFromBaseChain(baseTypeChain, datatypes) {
4131
4239
  if (chainString.includes("datetime") || chainString.includes("time")) {
4132
4240
  return {
4133
4241
  fieldType: "datetime",
4134
- htmlInputType: "datetime-local"
4242
+ htmlInputType: "datetime-local",
4243
+ min: "1900-01-01T00:00",
4244
+ max: "2100-12-31T23:59"
4135
4245
  };
4136
4246
  }
4137
4247
  return {
4138
4248
  fieldType: "date",
4139
- htmlInputType: "date"
4249
+ htmlInputType: "date",
4250
+ min: "1900-01-01",
4251
+ max: "2100-12-31"
4140
4252
  };
4141
4253
  }
4142
4254
  if (chainLower.some((type) => type.includes("boolean") || type.includes("bool"))) {
@@ -4167,48 +4279,262 @@ function determineInputTypeFromBaseChain(baseTypeChain, datatypes) {
4167
4279
  placeholder: I18n.t("field.enterPercentage")
4168
4280
  };
4169
4281
  }
4170
- if (chainLower.some((type) => type.includes("decimal") || type === "decimal")) {
4171
- if (chainString.includes("monetary") || chainString.includes("currency")) {
4172
- if (chainString.includes("nodecimals") || chainString.includes("no-decimals") || chainString.includes("nodecimals20")) {
4173
- return {
4174
- fieldType: "number",
4175
- htmlInputType: "text",
4176
- inputMode: "decimal",
4177
- allowDecimals: false,
4178
- step: 1,
4179
- placeholder: I18n.t("field.enterAmountNoDecimals")
4180
- };
4181
- }
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) {
4182
4483
  return {
4183
- fieldType: "currency",
4184
- htmlInputType: "text",
4185
- inputMode: "decimal",
4186
- allowDecimals: true,
4187
- step: 0.01,
4188
- 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
4189
4488
  };
4190
4489
  }
4191
- return {
4192
- fieldType: "decimal",
4193
- htmlInputType: "text",
4194
- inputMode: "decimal",
4195
- allowDecimals: true,
4196
- step: "any",
4197
- placeholder: I18n.t("field.enterDecimalValue")
4198
- };
4490
+ return null;
4199
4491
  }
4200
- if (baseTypeChain.some((type) => type === "xbrli:stringItemType") || chainLower.some((type) => type === "string" || type.includes("stringitem"))) {
4201
- return {
4202
- fieldType: "text",
4203
- htmlInputType: "text",
4204
- placeholder: I18n.t("field.enterText")
4205
- };
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;
4206
4537
  }
4207
- return {
4208
- fieldType: "text",
4209
- htmlInputType: "text",
4210
- placeholder: I18n.t("field.enterValue")
4211
- };
4212
4538
  }
4213
4539
  var __defProp$7 = Object.defineProperty;
4214
4540
  var __getOwnPropDesc$7 = Object.getOwnPropertyDescriptor;
@@ -4290,6 +4616,14 @@ let JupiterFormField = class extends LitElement {
4290
4616
  }
4291
4617
  _handleInput(event) {
4292
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
+ }
4293
4627
  const oldValue = this.value;
4294
4628
  let value = target.value;
4295
4629
  if (this._isNumericField()) {
@@ -4427,7 +4761,7 @@ let JupiterFormField = class extends LitElement {
4427
4761
  return;
4428
4762
  }
4429
4763
  const typeConfig = getInputTypeForConceptType(this.conceptType, this.datatypes);
4430
- const allowNegative = typeConfig.min === void 0 || typeConfig.min < 0;
4764
+ const allowNegative = typeConfig.min === void 0 || typeof typeConfig.min === "number" && typeConfig.min < 0;
4431
4765
  if (event.key === "-" && allowNegative && !target.value.includes("-") && (target.selectionStart === 0 || target.value.length === 0)) {
4432
4766
  return;
4433
4767
  }
@@ -4921,6 +5255,33 @@ let JupiterFormField = class extends LitElement {
4921
5255
  } else {
4922
5256
  renderedInputValue = effectiveValue ?? "";
4923
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
+ }
4924
5285
  return html`
4925
5286
  <input
4926
5287
  id="${fieldId}"
@@ -4935,6 +5296,7 @@ let JupiterFormField = class extends LitElement {
4935
5296
  min="${min !== void 0 ? min : ""}"
4936
5297
  max="${max !== void 0 ? max : ""}"
4937
5298
  @input="${this._handleInput}"
5299
+ @change="${this._handleInput}"
4938
5300
  @focus="${this._handleFocus}"
4939
5301
  @blur="${this._handleBlur}"
4940
5302
  @keydown="${this._handleKeyDown}"
@@ -4987,6 +5349,8 @@ let JupiterFormField = class extends LitElement {
4987
5349
  <div class="date-wrapper">
4988
5350
  <input
4989
5351
  type="date"
5352
+ min="1900-01-01"
5353
+ max="2100-12-31"
4990
5354
  .value="${this.periodInstantDate || this.field.periodInstantDate || ""}"
4991
5355
  @change="${(e2) => this._handlePeriodChange(e2, "instant")}"
4992
5356
  ?disabled="${this.disabled}"
@@ -5006,6 +5370,8 @@ let JupiterFormField = class extends LitElement {
5006
5370
  <div class="date-wrapper">
5007
5371
  <input
5008
5372
  type="date"
5373
+ min="1900-01-01"
5374
+ max="2100-12-31"
5009
5375
  .value="${this.periodStartDate || this.field.periodStartDate || ""}"
5010
5376
  @change="${(e2) => this._handlePeriodChange(e2, "start")}"
5011
5377
  ?disabled="${this.disabled}"
@@ -5020,6 +5386,8 @@ let JupiterFormField = class extends LitElement {
5020
5386
  <div class="date-wrapper">
5021
5387
  <input
5022
5388
  type="date"
5389
+ min="1900-01-01"
5390
+ max="2100-12-31"
5023
5391
  .value="${this.periodEndDate || this.field.periodEndDate || ""}"
5024
5392
  @change="${(e2) => this._handlePeriodChange(e2, "end")}"
5025
5393
  ?disabled="${this.disabled}"
@@ -5053,6 +5421,8 @@ let JupiterFormField = class extends LitElement {
5053
5421
  <div class="date-wrapper">
5054
5422
  <input
5055
5423
  type="date"
5424
+ min="1900-01-01"
5425
+ max="2100-12-31"
5056
5426
  .value="${this.periodInstantDate || this.field.periodInstantDate || ""}"
5057
5427
  @change="${(e2) => this._handlePeriodChange(e2, "instant")}"
5058
5428
  ?disabled="${this.disabled}"
@@ -5070,6 +5440,8 @@ let JupiterFormField = class extends LitElement {
5070
5440
  <div class="date-wrapper">
5071
5441
  <input
5072
5442
  type="date"
5443
+ min="1900-01-01"
5444
+ max="2100-12-31"
5073
5445
  .value="${this.periodStartDate || this.field.periodStartDate || ""}"
5074
5446
  @change="${(e2) => this._handlePeriodChange(e2, "start")}"
5075
5447
  ?disabled="${this.disabled}"
@@ -5088,6 +5460,8 @@ let JupiterFormField = class extends LitElement {
5088
5460
  <div class="date-wrapper">
5089
5461
  <input
5090
5462
  type="date"
5463
+ min="1900-01-01"
5464
+ max="2100-12-31"
5091
5465
  .value="${this.periodEndDate || this.field.periodEndDate || ""}"
5092
5466
  @change="${(e2) => this._handlePeriodChange(e2, "end")}"
5093
5467
  ?disabled="${this.disabled}"
@@ -5318,6 +5692,11 @@ JupiterFormField.styles = css`
5318
5692
  border-radius: 4px;
5319
5693
  transition: background-color 0.2s;
5320
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;
5321
5700
  }
5322
5701
 
5323
5702
  .period-icon-btn:hover {
@@ -6580,6 +6959,7 @@ let JupiterAddColumnDialog = class extends LitElement {
6580
6959
  this._instantDate = "";
6581
6960
  this._selectedType = "duration";
6582
6961
  this._selectedDimensions = /* @__PURE__ */ new Map();
6962
+ this._typedValueErrors = {};
6583
6963
  }
6584
6964
  updated(changedProperties) {
6585
6965
  if (changedProperties.has("open") && this.open) {
@@ -6643,6 +7023,7 @@ let JupiterAddColumnDialog = class extends LitElement {
6643
7023
  this._instantDate = e2.target.value;
6644
7024
  }
6645
7025
  _handleDimensionToggle(dimensionId, event) {
7026
+ var _a, _b;
6646
7027
  const checkbox = event.target;
6647
7028
  if (checkbox.checked) {
6648
7029
  const dimension = this.availableDimensions.find((d2) => d2.id === dimensionId);
@@ -6650,7 +7031,9 @@ let JupiterAddColumnDialog = class extends LitElement {
6650
7031
  const selection = {
6651
7032
  axisId: dimension.id,
6652
7033
  axisLabel: dimension.axisLabel,
6653
- 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
6654
7037
  };
6655
7038
  this._selectedDimensions.set(dimensionId, selection);
6656
7039
  }
@@ -6669,24 +7052,130 @@ let JupiterAddColumnDialog = class extends LitElement {
6669
7052
  }
6670
7053
  }
6671
7054
  _handleTypedValueChange(dimensionId, event) {
6672
- const input = event.target;
7055
+ const target = event.target;
7056
+ const value = target instanceof HTMLInputElement && target.type === "checkbox" ? String(target.checked) : target.value;
6673
7057
  const existing = this._selectedDimensions.get(dimensionId);
6674
7058
  if (existing) {
6675
- existing.typedValue = input.value;
7059
+ existing.typedValue = value;
6676
7060
  this._selectedDimensions.set(dimensionId, existing);
6677
7061
  }
6678
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
+ }
6679
7166
  _resetForm() {
6680
7167
  this._startDate = this.periodStartDate || "";
6681
7168
  this._endDate = this.periodEndDate || "";
6682
7169
  this._instantDate = this.periodEndDate || this.periodStartDate || "";
6683
7170
  this._selectedType = this.periodType === "instant" ? "instant" : "duration";
6684
7171
  this._selectedDimensions.clear();
7172
+ this._typedValueErrors = {};
6685
7173
  this._autoSelectSingleMemberDimensions();
6686
7174
  this.requestUpdate();
6687
7175
  }
6688
7176
  _autoSelectSingleMemberDimensions() {
6689
7177
  this.availableDimensions.forEach((dimension) => {
7178
+ var _a, _b;
6690
7179
  if (dimension.members && dimension.members.length > 0) {
6691
7180
  const member = dimension.members[0];
6692
7181
  const selection = {
@@ -6702,7 +7191,9 @@ let JupiterAddColumnDialog = class extends LitElement {
6702
7191
  const selection = {
6703
7192
  axisId: dimension.id,
6704
7193
  axisLabel: dimension.axisLabel,
6705
- 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
6706
7197
  // Note: typedValue will be entered in column header, not here
6707
7198
  };
6708
7199
  this._selectedDimensions.set(dimension.id, selection);
@@ -6728,6 +7219,8 @@ let JupiterAddColumnDialog = class extends LitElement {
6728
7219
  <div class="date-wrapper">
6729
7220
  <input
6730
7221
  type="date"
7222
+ min="1900-01-01"
7223
+ max="2100-12-31"
6731
7224
  class="form-input"
6732
7225
  .value="${this._instantDate}"
6733
7226
  @change="${this._handleInstantDateChange}"
@@ -6747,6 +7240,8 @@ let JupiterAddColumnDialog = class extends LitElement {
6747
7240
  <div class="date-wrapper">
6748
7241
  <input
6749
7242
  type="date"
7243
+ min="1900-01-01"
7244
+ max="2100-12-31"
6750
7245
  class="form-input"
6751
7246
  .value="${this._startDate}"
6752
7247
  @change="${this._handleStartDateChange}"
@@ -6764,6 +7259,8 @@ let JupiterAddColumnDialog = class extends LitElement {
6764
7259
  <div class="date-wrapper">
6765
7260
  <input
6766
7261
  type="date"
7262
+ min="1900-01-01"
7263
+ max="2100-12-31"
6767
7264
  class="form-input"
6768
7265
  .value="${this._endDate}"
6769
7266
  @change="${this._handleEndDateChange}"
@@ -6786,6 +7283,7 @@ let JupiterAddColumnDialog = class extends LitElement {
6786
7283
  <p class="form-description">${I18n.t("column.dimensionsDescription")}</p>
6787
7284
 
6788
7285
  ${this.availableDimensions.map((dimension) => {
7286
+ var _a, _b;
6789
7287
  const isSelected = this._selectedDimensions.has(dimension.id);
6790
7288
  const selection = this._selectedDimensions.get(dimension.id);
6791
7289
  const isTypedDimension = !!(dimension.typedMember && (!dimension.members || dimension.members.length === 0));
@@ -6810,13 +7308,8 @@ let JupiterAddColumnDialog = class extends LitElement {
6810
7308
  <!-- Typed Dimension Input -->
6811
7309
  <div class="member-selection">
6812
7310
  <label>${I18n.t("column.enterValue")} ${dimension.conceptName}:</label>
6813
- <input
6814
- type="text"
6815
- class="typed-input"
6816
- .value="${(selection == null ? void 0 : selection.typedValue) || ""}"
6817
- @input="${(e2) => this._handleTypedValueChange(dimension.id, e2)}"
6818
- placeholder="${I18n.t("column.enterValuePlaceholder")}"
6819
- />
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)}
6820
7313
  </div>
6821
7314
  ` : html`
6822
7315
  <!-- Domain Members Selection -->
@@ -7133,6 +7626,20 @@ JupiterAddColumnDialog.styles = css`
7133
7626
  font-size: 14px;
7134
7627
  }
7135
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
+
7136
7643
  .member-list {
7137
7644
  max-height: 120px;
7138
7645
  overflow-y: auto;
@@ -7188,6 +7695,9 @@ __decorateClass$5([
7188
7695
  __decorateClass$5([
7189
7696
  n2({ type: String })
7190
7697
  ], JupiterAddColumnDialog.prototype, "periodEndDate", 2);
7698
+ __decorateClass$5([
7699
+ n2({ type: Array })
7700
+ ], JupiterAddColumnDialog.prototype, "datatypes", 2);
7191
7701
  __decorateClass$5([
7192
7702
  r()
7193
7703
  ], JupiterAddColumnDialog.prototype, "_startDate", 2);
@@ -7203,6 +7713,9 @@ __decorateClass$5([
7203
7713
  __decorateClass$5([
7204
7714
  r()
7205
7715
  ], JupiterAddColumnDialog.prototype, "_selectedDimensions", 2);
7716
+ __decorateClass$5([
7717
+ r()
7718
+ ], JupiterAddColumnDialog.prototype, "_typedValueErrors", 2);
7206
7719
  JupiterAddColumnDialog = __decorateClass$5([
7207
7720
  t$1("jupiter-add-column-dialog")
7208
7721
  ], JupiterAddColumnDialog);
@@ -7247,6 +7760,7 @@ let JupiterFormSection = class extends LitElement {
7247
7760
  this._sectionPeriodType = "duration";
7248
7761
  this._openMenuColumnId = null;
7249
7762
  this._insertAfterColumnId = null;
7763
+ this._typedMemberErrors = {};
7250
7764
  this._boundFieldBlur = (e2) => {
7251
7765
  this._clearHighlights();
7252
7766
  this._focusedConceptId = null;
@@ -7868,9 +8382,72 @@ let JupiterFormSection = class extends LitElement {
7868
8382
  var _a;
7869
8383
  return ((_a = this.typedMemberData[columnId]) == null ? void 0 : _a[axisId]) || "";
7870
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
+ }
7871
8448
  _handleTypedMemberHeaderChange(event, columnId, axisId) {
7872
8449
  const target = event.target;
7873
- const value = target.value;
8450
+ const value = target instanceof HTMLInputElement && target.type === "checkbox" ? String(target.checked) : target.value;
7874
8451
  console.log(`🔍 [FormSection] Typed member header change: columnId=${columnId}, axisId=${axisId}, value=${value}`);
7875
8452
  this.dispatchEvent(new CustomEvent("typed-member-change", {
7876
8453
  detail: {
@@ -7881,6 +8458,69 @@ let JupiterFormSection = class extends LitElement {
7881
8458
  bubbles: true
7882
8459
  }));
7883
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
+ }
7884
8524
  _handleAddRepeat(event) {
7885
8525
  event.stopPropagation();
7886
8526
  this.dispatchEvent(new CustomEvent("add-concept-repeat", {
@@ -8024,23 +8664,24 @@ let JupiterFormSection = class extends LitElement {
8024
8664
  <!-- Typed member input fields in column header -->
8025
8665
  ${((_a = column2.dimensionData) == null ? void 0 : _a.hasTypedMembers) ? html`
8026
8666
  <div class="typed-members-header">
8027
- ${(_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`
8028
8670
  <div class="typed-member-header-input">
8029
8671
  ${this.mode === "readonly" ? html`
8030
8672
  <span class="typed-member-header-field" style="display:block;padding:4px 0;">
8031
8673
  ${this._getTypedMemberHeaderValue(column2.id, typedMember.axisId) || "—"}
8032
8674
  </span>
8033
8675
  ` : html`
8034
- <input
8035
- type="text"
8036
- class="typed-member-header-field"
8037
- placeholder="${I18n.t("section.enterPlaceholder")} ${typedMember.axisLabel}"
8038
- .value="${this._getTypedMemberHeaderValue(column2.id, typedMember.axisId)}"
8039
- @input="${(e2) => this._handleTypedMemberHeaderChange(e2, column2.id, typedMember.axisId)}"
8040
- />
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)}
8041
8681
  `}
8042
8682
  </div>
8043
- `)}
8683
+ `;
8684
+ })}
8044
8685
  </div>
8045
8686
  ` : ""}
8046
8687
  </div>
@@ -8113,6 +8754,7 @@ let JupiterFormSection = class extends LitElement {
8113
8754
  .availableDimensions="${this.availableDimensions}"
8114
8755
  .periodStartDate="${this.periodStartDate}"
8115
8756
  .periodEndDate="${this.periodEndDate}"
8757
+ .datatypes="${this.datatypes}"
8116
8758
  @dialog-cancel="${this._handleDialogCancel}"
8117
8759
  @column-add="${this._handleColumnAdd}"
8118
8760
  ></jupiter-add-column-dialog>
@@ -8366,6 +9008,34 @@ JupiterFormSection.styles = css`
8366
9008
  gap: 2px;
8367
9009
  }
8368
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
+
8369
9039
  .typed-member-header-label {
8370
9040
  font-size: 11px;
8371
9041
  font-weight: 500;
@@ -8494,6 +9164,9 @@ __decorateClass$4([
8494
9164
  __decorateClass$4([
8495
9165
  r()
8496
9166
  ], JupiterFormSection.prototype, "_insertAfterColumnId", 2);
9167
+ __decorateClass$4([
9168
+ r()
9169
+ ], JupiterFormSection.prototype, "_typedMemberErrors", 2);
8497
9170
  __decorateClass$4([
8498
9171
  r()
8499
9172
  ], JupiterFormSection.prototype, "_expandedConcepts", 2);
@@ -10899,6 +11572,7 @@ let JupiterDynamicForm = class extends LitElement {
10899
11572
  this.readonly = false;
10900
11573
  this.calculationEnabled = false;
10901
11574
  this.formulaEnabled = false;
11575
+ this.tableLayoutEnabled = false;
10902
11576
  this.periodStartDate = "2025-01-01";
10903
11577
  this.periodEndDate = "2025-12-31";
10904
11578
  this.language = "en";
@@ -10908,6 +11582,7 @@ let JupiterDynamicForm = class extends LitElement {
10908
11582
  this.roleFilterAxes = [];
10909
11583
  this.showLastValidationResultBtn = false;
10910
11584
  this.isDraftSaving = false;
11585
+ this.hasUnsavedChanges = false;
10911
11586
  this.defaultUnits = [];
10912
11587
  this.decimals = "INF";
10913
11588
  this._formData = {};
@@ -10996,6 +11671,7 @@ let JupiterDynamicForm = class extends LitElement {
10996
11671
  const oldValue = this._writeFormDataValue(conceptId, columnId, value);
10997
11672
  this._touched.add(`${conceptId}-${columnId}`);
10998
11673
  this._dirty = true;
11674
+ this.hasUnsavedChanges = true;
10999
11675
  this._validateForm();
11000
11676
  this.requestUpdate();
11001
11677
  this.dispatchEvent(new CustomEvent("field-change", {
@@ -11033,6 +11709,12 @@ let JupiterDynamicForm = class extends LitElement {
11033
11709
  }
11034
11710
  }
11035
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
+ }
11036
11718
  if (changedProperties.has("decimals") || changedProperties.has("masterData")) {
11037
11719
  this._computeEffectiveMasterData();
11038
11720
  }
@@ -11437,22 +12119,34 @@ let JupiterDynamicForm = class extends LitElement {
11437
12119
  *
11438
12120
  * Skipped when form state is being restored from external props or localStorage so that
11439
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.
11440
12126
  */
11441
12127
  _applyDimensionShowPreviousYear() {
11442
- var _a, _b;
12128
+ var _a, _b, _c, _d, _e, _f;
11443
12129
  if (this.mode !== "admin")
11444
12130
  return;
11445
12131
  const hasExternalState = !!(this.dynaformsFacts && this.dynaformsMetadata);
11446
12132
  const hasLocalDraft = ((_a = this._draftStorageService) == null ? void 0 : _a.hasDraft()) ?? false;
11447
12133
  if (hasExternalState || hasLocalDraft)
11448
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
+ });
11449
12141
  const updated = { ...this._periodPreferences };
11450
12142
  let changed = false;
11451
12143
  for (const roleId of Object.keys(updated)) {
12144
+ if (explicitlyDisabled.has(roleId))
12145
+ continue;
11452
12146
  const prefs = updated[roleId];
11453
12147
  if (prefs.showPreviousYear)
11454
12148
  continue;
11455
- const hasDimensionMembers = ((_b = prefs.dimensionSelections) == null ? void 0 : _b.some(
12149
+ const hasDimensionMembers = ((_f = prefs.dimensionSelections) == null ? void 0 : _f.some(
11456
12150
  (ds) => ds.selectedMemberIds && ds.selectedMemberIds.length > 0
11457
12151
  )) ?? false;
11458
12152
  if (hasDimensionMembers) {
@@ -11851,6 +12545,7 @@ let JupiterDynamicForm = class extends LitElement {
11851
12545
  console.log(`💾 Updated formData[${conceptId}]:`, this._formData[conceptId]);
11852
12546
  this._touched.add(`${conceptId}-${columnId}`);
11853
12547
  this._dirty = true;
12548
+ this.hasUnsavedChanges = true;
11854
12549
  this._validateForm();
11855
12550
  this.requestUpdate();
11856
12551
  this.dispatchEvent(new CustomEvent("field-change", {
@@ -11882,6 +12577,7 @@ let JupiterDynamicForm = class extends LitElement {
11882
12577
  this._storeUnit(conceptId, columnId, unit);
11883
12578
  }
11884
12579
  this._dirty = true;
12580
+ this.hasUnsavedChanges = true;
11885
12581
  this.requestUpdate();
11886
12582
  }
11887
12583
  _handleUnitChange(event) {
@@ -11891,6 +12587,7 @@ let JupiterDynamicForm = class extends LitElement {
11891
12587
  console.log(`đŸˇī¸ Unit change received: conceptId=${conceptId}, columnId=${columnId}, unit=${unit}`);
11892
12588
  this._storeUnit(conceptId, columnId, unit);
11893
12589
  this._dirty = true;
12590
+ this.hasUnsavedChanges = true;
11894
12591
  this.requestUpdate();
11895
12592
  }
11896
12593
  _storeUnit(conceptId, columnId, unit) {
@@ -11915,6 +12612,7 @@ let JupiterDynamicForm = class extends LitElement {
11915
12612
  return;
11916
12613
  this._storeDecimals(conceptId, columnId, decimals);
11917
12614
  this._dirty = true;
12615
+ this.hasUnsavedChanges = true;
11918
12616
  this.requestUpdate();
11919
12617
  }
11920
12618
  _computeEffectiveMasterData() {
@@ -12056,6 +12754,7 @@ let JupiterDynamicForm = class extends LitElement {
12056
12754
  const { conceptId } = event.detail;
12057
12755
  this._repeatCounts = { ...this._repeatCounts, [conceptId]: (this._repeatCounts[conceptId] || 0) + 1 };
12058
12756
  this._dirty = true;
12757
+ this.hasUnsavedChanges = true;
12059
12758
  }
12060
12759
  _handleRemoveConceptRepeat(event) {
12061
12760
  const { conceptId: rawConceptId } = event.detail;
@@ -12108,6 +12807,7 @@ let JupiterDynamicForm = class extends LitElement {
12108
12807
  this._periodData = newPeriodData;
12109
12808
  this._repeatCounts = { ...this._repeatCounts, [baseConceptId]: current - 1 };
12110
12809
  this._dirty = true;
12810
+ this.hasUnsavedChanges = true;
12111
12811
  }
12112
12812
  _handleTypedMemberChange(event) {
12113
12813
  const { columnId, axisId, value, conceptId } = event.detail;
@@ -12120,6 +12820,7 @@ let JupiterDynamicForm = class extends LitElement {
12120
12820
  updatedTypedMemberData[storageKey] = { ...updatedTypedMemberData[storageKey], [axisId]: value };
12121
12821
  this._typedMemberData = updatedTypedMemberData;
12122
12822
  this._dirty = true;
12823
+ this.hasUnsavedChanges = true;
12123
12824
  this.requestUpdate();
12124
12825
  this.dispatchEvent(new CustomEvent("typed-member-change", {
12125
12826
  detail: { columnId, axisId, value },
@@ -12318,6 +13019,7 @@ let JupiterDynamicForm = class extends LitElement {
12318
13019
  }
12319
13020
  this._removeColumnData(columnId, sectionId);
12320
13021
  this._dirty = true;
13022
+ this.hasUnsavedChanges = true;
12321
13023
  this._validateForm();
12322
13024
  this.requestUpdate();
12323
13025
  this.dispatchEvent(new CustomEvent("column-remove", {
@@ -12334,6 +13036,7 @@ let JupiterDynamicForm = class extends LitElement {
12334
13036
  }
12335
13037
  this._hiddenColumnIds.get(sectionId).add(columnId);
12336
13038
  this._dirty = true;
13039
+ this.hasUnsavedChanges = true;
12337
13040
  this.requestUpdate();
12338
13041
  this.dispatchEvent(new CustomEvent("column-hide", {
12339
13042
  detail: { columnId, sectionId },
@@ -12347,6 +13050,7 @@ let JupiterDynamicForm = class extends LitElement {
12347
13050
  return;
12348
13051
  (_a = this._hiddenColumnIds.get(sectionId)) == null ? void 0 : _a.delete(columnId);
12349
13052
  this._dirty = true;
13053
+ this.hasUnsavedChanges = true;
12350
13054
  this.requestUpdate();
12351
13055
  this.dispatchEvent(new CustomEvent("column-show", {
12352
13056
  detail: { columnId, sectionId },
@@ -12410,8 +13114,8 @@ let JupiterDynamicForm = class extends LitElement {
12410
13114
  if (dimension.typedMember) {
12411
13115
  availableDimension.typedMember = {
12412
13116
  id: dimension.typedMember.id,
12413
- dataType: "string"
12414
- // Default to string for now
13117
+ dataType: dimension.typedMember.type || "string",
13118
+ validation: dimension.typedMember.validation
12415
13119
  };
12416
13120
  }
12417
13121
  availableDimensions.push(availableDimension);
@@ -12632,8 +13336,10 @@ let JupiterDynamicForm = class extends LitElement {
12632
13336
  axisLabel: dim.axisLabel,
12633
13337
  typedMemberId: dim.axisId,
12634
13338
  // Use axisId as typedMemberId for typed dimensions
12635
- memberLabel: dim.axisLabel
13339
+ memberLabel: dim.axisLabel,
12636
13340
  // Use axisLabel as memberLabel
13341
+ type: dim.dataType,
13342
+ validation: dim.validation
12637
13343
  }));
12638
13344
  }
12639
13345
  }
@@ -12662,6 +13368,7 @@ let JupiterDynamicForm = class extends LitElement {
12662
13368
  }
12663
13369
  this._replicateFieldsForNewColumn(newColumnId, request, sectionId);
12664
13370
  this._dirty = true;
13371
+ this.hasUnsavedChanges = true;
12665
13372
  this.requestUpdate();
12666
13373
  this.dispatchEvent(new CustomEvent("column-add", {
12667
13374
  detail: { column: newColumn },
@@ -12981,9 +13688,11 @@ let JupiterDynamicForm = class extends LitElement {
12981
13688
  });
12982
13689
  if (draftPayloadSnapshot === this._lastSavedDraftSnapshot) {
12983
13690
  console.log(`â„šī¸ [Save Draft] No draft content change detected (${source}); skipping event emit.`);
13691
+ this.hasUnsavedChanges = false;
12984
13692
  return;
12985
13693
  }
12986
13694
  this._lastSavedDraftSnapshot = draftPayloadSnapshot;
13695
+ this.hasUnsavedChanges = false;
12987
13696
  const saved = this._draftStorageService.saveDraft(draftData, metadata);
12988
13697
  console.log("💾 [Save Draft] Metadata saved with enhanced selectedRoleIds:", metadata.selectedRoleIds);
12989
13698
  this.dynaformsFacts = draftData;
@@ -14067,6 +14776,7 @@ let JupiterDynamicForm = class extends LitElement {
14067
14776
  this._formData = { ...this.initialData };
14068
14777
  this._touched.clear();
14069
14778
  this._dirty = false;
14779
+ this.hasUnsavedChanges = false;
14070
14780
  this._submitted = false;
14071
14781
  this._preservedFormData = {};
14072
14782
  this._preservedTypedMemberData = {};
@@ -15010,6 +15720,7 @@ let JupiterDynamicForm = class extends LitElement {
15010
15720
  setData(data) {
15011
15721
  this._formData = { ...data };
15012
15722
  this._dirty = true;
15723
+ this.hasUnsavedChanges = true;
15013
15724
  this._validateForm();
15014
15725
  this.requestUpdate();
15015
15726
  }
@@ -15438,7 +16149,7 @@ let JupiterDynamicForm = class extends LitElement {
15438
16149
  <button
15439
16150
  class="btn-secondary"
15440
16151
  @click="${this._handleSaveDraft}"
15441
- ?disabled="${this.disabled || this.readonly || this.isDraftSaving}"
16152
+ ?disabled="${this.disabled || this.readonly || this.isDraftSaving || !this.hasUnsavedChanges}"
15442
16153
  >
15443
16154
  ${this.isDraftSaving ? I18n.t("form.savingDraft") : I18n.t("form.saveDraft")}
15444
16155
  </button>
@@ -16436,6 +17147,9 @@ __decorateClass([
16436
17147
  __decorateClass([
16437
17148
  n2({ type: Boolean, attribute: "formula-enabled" })
16438
17149
  ], JupiterDynamicForm.prototype, "formulaEnabled", 2);
17150
+ __decorateClass([
17151
+ n2({ type: Boolean, attribute: "table-layout-enabled" })
17152
+ ], JupiterDynamicForm.prototype, "tableLayoutEnabled", 2);
16439
17153
  __decorateClass([
16440
17154
  n2({ type: String })
16441
17155
  ], JupiterDynamicForm.prototype, "periodStartDate", 2);
@@ -16466,6 +17180,9 @@ __decorateClass([
16466
17180
  __decorateClass([
16467
17181
  n2({ type: Boolean, attribute: "is-draft-saving" })
16468
17182
  ], JupiterDynamicForm.prototype, "isDraftSaving", 2);
17183
+ __decorateClass([
17184
+ n2({ type: Boolean, attribute: "has-unsaved-changes", reflect: true })
17185
+ ], JupiterDynamicForm.prototype, "hasUnsavedChanges", 2);
16469
17186
  __decorateClass([
16470
17187
  n2({ type: Array })
16471
17188
  ], JupiterDynamicForm.prototype, "defaultUnits", 2);