jupiter-dynamic-forms 1.22.3 → 1.22.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1808,6 +1808,7 @@ const field$1 = {
1808
1808
  selectUnit: "Select Unit",
1809
1809
  leaveBlankForInf: "Leave blank for INF",
1810
1810
  prefilledLocked: "Pre-filled — cannot be changed here",
1811
+ notApplicablePriorYear: "Not required for the previous year",
1811
1812
  invalidNumber: `Can't read "{{value}}" as a number`,
1812
1813
  noDecimalsAllowed: "This field doesn't accept decimals",
1813
1814
  negativeNotAllowed: "A negative value isn't allowed here",
@@ -1857,6 +1858,18 @@ const typedDimensionWarning$1 = {
1857
1858
  "continue": "Continue to validate",
1858
1859
  cancel: "Cancel"
1859
1860
  };
1861
+ const validateWarningPopup$1 = {
1862
+ title: "Validation warnings"
1863
+ };
1864
+ const duplicateFactWarning$1 = {
1865
+ title: "Duplicate facts with different values",
1866
+ message: "The same concept, period and dimensions were found with different values entered in different roles. Only one value can be correct — click a fact below to review and fix it.",
1867
+ role: "Role:",
1868
+ value: "Value:",
1869
+ clickToFocus: "Click to go to this fact",
1870
+ cellTooltip: "This value differs from the same fact entered elsewhere — click to review",
1871
+ close: "Close"
1872
+ };
1860
1873
  const calculationWarning$1 = {
1861
1874
  title: "Calculation warning",
1862
1875
  summaryLine: 'Total "{{label}}" should equal {{calculated}}, but you entered {{entered}}.',
@@ -1877,7 +1890,8 @@ const formulaValidation$1 = {
1877
1890
  summaryCountBreakdown: "{{warnings}} warnings, {{errors}} errors",
1878
1891
  severityWarning: "Warning",
1879
1892
  severityError: "Error",
1880
- close: "Close"
1893
+ close: "Close",
1894
+ breakdownLabel: "Values checked:"
1881
1895
  };
1882
1896
  const scrollToConcept$1 = {
1883
1897
  revealRole: {
@@ -1899,6 +1913,8 @@ const enTranslations = {
1899
1913
  xbrlValidation: xbrlValidation$1,
1900
1914
  error: error$1,
1901
1915
  typedDimensionWarning: typedDimensionWarning$1,
1916
+ validateWarningPopup: validateWarningPopup$1,
1917
+ duplicateFactWarning: duplicateFactWarning$1,
1902
1918
  calculationWarning: calculationWarning$1,
1903
1919
  balanceCheck: balanceCheck$1,
1904
1920
  formulaValidation: formulaValidation$1,
@@ -2055,6 +2071,7 @@ const field = {
2055
2071
  selectUnit: "Selecteer eenheid",
2056
2072
  leaveBlankForInf: "Leeg laten voor INF",
2057
2073
  prefilledLocked: "Vooraf ingevuld — kan hier niet worden gewijzigd",
2074
+ notApplicablePriorYear: "Niet vereist voor het voorgaande jaar",
2058
2075
  invalidNumber: "Kan '{{value}}' niet lezen als getal",
2059
2076
  noDecimalsAllowed: "Dit veld accepteert geen decimalen",
2060
2077
  negativeNotAllowed: "Een negatieve waarde is hier niet toegestaan",
@@ -2104,6 +2121,18 @@ const typedDimensionWarning = {
2104
2121
  "continue": "Doorgaan met valideren",
2105
2122
  cancel: "Annuleren"
2106
2123
  };
2124
+ const validateWarningPopup = {
2125
+ title: "Validatiewaarschuwingen"
2126
+ };
2127
+ const duplicateFactWarning = {
2128
+ title: "Dubbele feiten met verschillende waarden",
2129
+ message: "Hetzelfde concept, dezelfde periode en dimensies zijn gevonden met verschillende waarden in verschillende rollen. Slechts één waarde kan juist zijn — klik op een feit hieronder om het te controleren en te corrigeren.",
2130
+ role: "Rol:",
2131
+ value: "Waarde:",
2132
+ clickToFocus: "Klik om naar dit feit te gaan",
2133
+ cellTooltip: "Deze waarde wijkt af van hetzelfde feit dat elders is ingevoerd — klik om te controleren",
2134
+ close: "Sluiten"
2135
+ };
2107
2136
  const calculationWarning = {
2108
2137
  title: "Berekeningswaarschuwing",
2109
2138
  summaryLine: 'Totaal "{{label}}" zou {{calculated}} moeten zijn, maar u heeft {{entered}} ingevoerd.',
@@ -2124,7 +2153,8 @@ const formulaValidation = {
2124
2153
  summaryCountBreakdown: "{{warnings}} waarschuwingen, {{errors}} fouten",
2125
2154
  severityWarning: "Waarschuwing",
2126
2155
  severityError: "Fout",
2127
- close: "Sluiten"
2156
+ close: "Sluiten",
2157
+ breakdownLabel: "Gecontroleerde waarden:"
2128
2158
  };
2129
2159
  const scrollToConcept = {
2130
2160
  revealRole: {
@@ -2146,6 +2176,8 @@ const nlTranslations = {
2146
2176
  xbrlValidation,
2147
2177
  error,
2148
2178
  typedDimensionWarning,
2179
+ validateWarningPopup,
2180
+ duplicateFactWarning,
2149
2181
  calculationWarning,
2150
2182
  balanceCheck,
2151
2183
  formulaValidation,
@@ -2528,6 +2560,19 @@ class Parser {
2528
2560
  }
2529
2561
  parseBoolTerm() {
2530
2562
  const token = this.peek();
2563
+ if (token.type === "LPAREN") {
2564
+ const savedPos = this.pos;
2565
+ try {
2566
+ this.next();
2567
+ const inner = this.parseOrExpr();
2568
+ this.expect("RPAREN");
2569
+ return inner;
2570
+ } catch (error2) {
2571
+ if (!(error2 instanceof FormulaExpressionError))
2572
+ throw error2;
2573
+ this.pos = savedPos;
2574
+ }
2575
+ }
2531
2576
  if (token.type === "IDENT" && token.value === "not") {
2532
2577
  this.next();
2533
2578
  this.expect("LPAREN");
@@ -2988,8 +3033,8 @@ function collectDescendantConceptIds(childrenIndex, rootConceptId) {
2988
3033
  function candidateKey(candidate) {
2989
3034
  return `${candidate.conceptId}::${candidate.columnId}`;
2990
3035
  }
2991
- function resolveMatchedCandidates(variable, ctx) {
2992
- return intersectCandidates(variable.filters.map((filter2) => resolveFilter(filter2, ctx.allCandidates, ctx, variable.filters)));
3036
+ function resolveMatchedCandidates(variable, ctx, roleURI) {
3037
+ return intersectCandidates(variable.filters.map((filter2) => resolveFilter(filter2, ctx.allCandidates, ctx, variable.filters, roleURI)));
2993
3038
  }
2994
3039
  function getColumn(ctx, candidate) {
2995
3040
  var _a;
@@ -3103,7 +3148,7 @@ function resolveCrossRoleChildren(anchorQName, ownLinkrole, ctx) {
3103
3148
  const otherRoles = Array.from(candidatesByRole.entries()).filter(([roleURI]) => roleURI !== ownLinkrole);
3104
3149
  return otherRoles.length === 1 ? otherRoles[0][1] : [];
3105
3150
  }
3106
- function matchFilter(filter2, universe, ctx, siblingFilters) {
3151
+ function matchFilter(filter2, universe, ctx, siblingFilters, roleURI) {
3107
3152
  var _a, _b;
3108
3153
  switch (filter2.type) {
3109
3154
  case "CONCEPT_NAME": {
@@ -3111,7 +3156,9 @@ function matchFilter(filter2, universe, ctx, siblingFilters) {
3111
3156
  const infos = qname ? ctx.conceptByQName.get(qname) : void 0;
3112
3157
  if (!infos || infos.length === 0)
3113
3158
  return [];
3114
- const conceptIds = new Set(infos.map((info) => info.conceptId));
3159
+ const ownRoleInfos = roleURI ? infos.filter((info) => ctx.roleURIByConceptId.get(info.conceptId) === roleURI) : [];
3160
+ const scopedInfos = ownRoleInfos.length > 0 ? ownRoleInfos : infos;
3161
+ const conceptIds = new Set(scopedInfos.map((info) => info.conceptId));
3115
3162
  return universe.filter((candidate) => conceptIds.has(candidate.conceptId));
3116
3163
  }
3117
3164
  case "CONCEPT_BALANCE": {
@@ -3171,7 +3218,7 @@ function matchFilter(filter2, universe, ctx, siblingFilters) {
3171
3218
  return universe.filter((candidate) => columnMatchesPeriod(getColumn(ctx, candidate), dateAttribute, ctx));
3172
3219
  }
3173
3220
  case "OR_FILTER":
3174
- return unionCandidates(filter2.children.map((child) => resolveFilter(child, universe, ctx, siblingFilters)));
3221
+ return unionCandidates(filter2.children.map((child) => resolveFilter(child, universe, ctx, siblingFilters, roleURI)));
3175
3222
  case "ASPECT_COVER":
3176
3223
  return universe;
3177
3224
  case "UNKNOWN": {
@@ -3189,8 +3236,8 @@ function matchFilter(filter2, universe, ctx, siblingFilters) {
3189
3236
  return [];
3190
3237
  }
3191
3238
  }
3192
- function resolveFilter(filter2, universe, ctx, siblingFilters) {
3193
- const matched = matchFilter(filter2, universe, ctx, siblingFilters);
3239
+ function resolveFilter(filter2, universe, ctx, siblingFilters, roleURI) {
3240
+ const matched = matchFilter(filter2, universe, ctx, siblingFilters, roleURI);
3194
3241
  if (!filter2.complement)
3195
3242
  return matched;
3196
3243
  const matchedKeys = new Set(matched.map(candidateKey));
@@ -3220,12 +3267,9 @@ function hasExplicitPeriodFilter(filters) {
3220
3267
  });
3221
3268
  }
3222
3269
  function narrowToCurrentPeriod(candidates, variable, ctx) {
3223
- if (candidates.length <= 1 || hasExplicitPeriodFilter(variable.filters))
3270
+ if (candidates.length === 0 || hasExplicitPeriodFilter(variable.filters))
3224
3271
  return candidates;
3225
- const currentPeriodOnly = candidates.filter(
3226
- (candidate) => columnEffectivePeriod(getColumn(ctx, candidate), ctx).end === ctx.periodEndDate
3227
- );
3228
- return currentPeriodOnly.length > 0 ? currentPeriodOnly : candidates;
3272
+ return candidates.filter((candidate) => columnEffectivePeriod(getColumn(ctx, candidate), ctx).end === ctx.periodEndDate);
3229
3273
  }
3230
3274
  function narrowByPreferredOccurrence(candidates, ctx) {
3231
3275
  const groups = /* @__PURE__ */ new Map();
@@ -3272,9 +3316,13 @@ function narrowToDirectChildOfDescendantAnchor(candidates, variable, ctx) {
3272
3316
  return candidates;
3273
3317
  return candidates.filter((candidate) => directChildren.has(candidate.conceptId));
3274
3318
  }
3275
- function resolveNarrowedCandidates(variable, ctx) {
3319
+ function resolveNarrowedCandidates(variable, ctx, roleURI) {
3276
3320
  return narrowByPreferredOccurrence(
3277
- narrowToDirectChildOfDescendantAnchor(narrowToCurrentPeriod(resolveMatchedCandidates(variable, ctx), variable, ctx), variable, ctx),
3321
+ narrowToDirectChildOfDescendantAnchor(
3322
+ narrowToCurrentPeriod(resolveMatchedCandidates(variable, ctx, roleURI), variable, ctx),
3323
+ variable,
3324
+ ctx
3325
+ ),
3278
3326
  ctx
3279
3327
  );
3280
3328
  }
@@ -3339,9 +3387,9 @@ function findUncoveredAxisAnchor(variables, axisId, candidatesByName, ctx) {
3339
3387
  }
3340
3388
  return void 0;
3341
3389
  }
3342
- function resolveAssertionVariableBindings(variables, ctx, implicitFiltering) {
3390
+ function computeAssertionNarrowedCandidatesByName(variables, ctx, implicitFiltering, roleURI) {
3343
3391
  const candidatesByName = /* @__PURE__ */ new Map();
3344
- variables.forEach((variable) => candidatesByName.set(variable.name, resolveNarrowedCandidates(variable, ctx)));
3392
+ variables.forEach((variable) => candidatesByName.set(variable.name, resolveNarrowedCandidates(variable, ctx, roleURI)));
3345
3393
  if (implicitFiltering) {
3346
3394
  const axisIds = /* @__PURE__ */ new Set();
3347
3395
  variables.forEach((variable) => uncoveredExplicitDimensionAxisIds(variable, ctx).forEach((axisId) => axisIds.add(axisId)));
@@ -3362,10 +3410,35 @@ function resolveAssertionVariableBindings(variables, ctx, implicitFiltering) {
3362
3410
  });
3363
3411
  });
3364
3412
  }
3413
+ return candidatesByName;
3414
+ }
3415
+ function resolveAssertionVariableBindings(variables, ctx, implicitFiltering, roleURI) {
3416
+ const candidatesByName = computeAssertionNarrowedCandidatesByName(variables, ctx, implicitFiltering, roleURI);
3365
3417
  const bindings = /* @__PURE__ */ new Map();
3366
3418
  variables.forEach((variable) => bindings.set(variable.name, valuesFromCandidates(variable, candidatesByName.get(variable.name) || [], ctx)));
3367
3419
  return bindings;
3368
3420
  }
3421
+ function buildFormulaVariableBreakdown(variables, ctx, implicitFiltering, roleURI) {
3422
+ const sequenceVariables = variables.filter((variable) => variable.bindAsSequence);
3423
+ if (sequenceVariables.length === 0)
3424
+ return void 0;
3425
+ const candidatesByName = computeAssertionNarrowedCandidatesByName(variables, ctx, implicitFiltering, roleURI);
3426
+ const breakdown = [];
3427
+ for (const variable of sequenceVariables) {
3428
+ const candidates = (candidatesByName.get(variable.name) || []).map((candidate) => {
3429
+ var _a, _b;
3430
+ return {
3431
+ conceptId: candidate.conceptId,
3432
+ conceptQName: (_a = ctx.conceptById.get(candidate.conceptId)) == null ? void 0 : _a.qname,
3433
+ columnId: candidate.columnId,
3434
+ value: resolveRawValue((_b = ctx.formData[candidate.conceptId]) == null ? void 0 : _b[candidate.columnId])
3435
+ };
3436
+ }).filter((candidate) => candidate.value !== void 0 && candidate.conceptQName !== void 0);
3437
+ if (candidates.length > 0)
3438
+ breakdown.push({ variableName: variable.name, candidates });
3439
+ }
3440
+ return breakdown.length > 0 ? breakdown : void 0;
3441
+ }
3369
3442
  function filterSignature(filter2) {
3370
3443
  const attrs = Object.entries(filter2.attributes).filter(([key]) => key !== "id").sort(([a2], [b2]) => a2.localeCompare(b2));
3371
3444
  return JSON.stringify({ type: filter2.type, complement: filter2.complement, cover: filter2.cover, attrs });
@@ -3520,8 +3593,8 @@ function resolveVariableColumnsForConcept(variable, targetConceptId, ctx) {
3520
3593
  const resolved = intersectCandidates(variable.filters.map((filter2) => resolveFilter(filter2, universe, ctx, variable.filters)));
3521
3594
  return resolved.map((candidate) => candidate.columnId);
3522
3595
  }
3523
- function buildAssertionBindings(assertion, ctx) {
3524
- return resolveAssertionVariableBindings(assertion.variables, ctx, assertion.implicitFiltering);
3596
+ function buildAssertionBindings(assertion, ctx, roleURI) {
3597
+ return resolveAssertionVariableBindings(assertion.variables, ctx, assertion.implicitFiltering, roleURI);
3525
3598
  }
3526
3599
  function buildAssertionFallbackUsage(assertion, ctx) {
3527
3600
  var _a;
@@ -3613,7 +3686,7 @@ class FormulaValidationService {
3613
3686
  }
3614
3687
  _evaluateAssertion(assertion, role, ctx, language) {
3615
3688
  try {
3616
- const bindings = buildAssertionBindings(assertion, ctx);
3689
+ const bindings = buildAssertionBindings(assertion, ctx, role.roleURI);
3617
3690
  const sumOfMembersShape = detectSumOfMembersOnAbstractShape(assertion);
3618
3691
  const sumOfMembersResult = sumOfMembersShape ? resolveSumOfMembersOnAbstractSatisfaction(sumOfMembersShape, ctx) : void 0;
3619
3692
  const usesSumOfMembersPath = sumOfMembersResult !== void 0 && sumOfMembersResult !== "not-applicable";
@@ -3640,7 +3713,8 @@ class FormulaValidationService {
3640
3713
  return this._evaluatedResult(assertion, role, ctx, sumOfMembersResult, language);
3641
3714
  }
3642
3715
  const satisfied = evaluateFormulaTest(assertion.test, bindings, fallbackUsage);
3643
- return this._evaluatedResult(assertion, role, ctx, satisfied, language);
3716
+ const variableBreakdown = satisfied ? void 0 : buildFormulaVariableBreakdown(assertion.variables, ctx, assertion.implicitFiltering, role.roleURI);
3717
+ return this._evaluatedResult(assertion, role, ctx, satisfied, language, void 0, variableBreakdown);
3644
3718
  } catch (error2) {
3645
3719
  if (error2 instanceof FormulaExpressionError) {
3646
3720
  console.warn(`[FormulaValidationService] Assertion "${assertion.id}" could not be evaluated (${error2.reason}): ${error2.message}`);
@@ -3661,7 +3735,7 @@ class FormulaValidationService {
3661
3735
  skipReason
3662
3736
  };
3663
3737
  }
3664
- _evaluatedResult(assertion, role, ctx, satisfied, language, matchedFactCount) {
3738
+ _evaluatedResult(assertion, role, ctx, satisfied, language, matchedFactCount, variableBreakdown) {
3665
3739
  const message = satisfied ? void 0 : this._pickMessage(assertion, language);
3666
3740
  return {
3667
3741
  assertionId: assertion.id,
@@ -3673,7 +3747,8 @@ class FormulaValidationService {
3673
3747
  skipped: false,
3674
3748
  message,
3675
3749
  matchedFactCount,
3676
- conceptLinks: message ? this._buildConceptLinks(assertion, role.roleURI, ctx, message) : void 0
3750
+ conceptLinks: message ? this._buildConceptLinks(assertion, role.roleURI, ctx, message) : void 0,
3751
+ variableBreakdown
3677
3752
  };
3678
3753
  }
3679
3754
  _pickMessage(assertion, language) {
@@ -4942,7 +5017,7 @@ let JupiterFormField = class extends LitElement {
4942
5017
  if (changedProperties.has("value") || changedProperties.has("field")) {
4943
5018
  this._validateField();
4944
5019
  }
4945
- if (changedProperties.has("unit") && this.unit) {
5020
+ if (changedProperties.has("unit") && this.unit && this.hasUpdated) {
4946
5021
  console.log(`🏷️ [FormField willUpdate] Unit property changed to: ${this.unit}, conceptId: ${this.conceptId}, columnId: ${this.columnId}`);
4947
5022
  this.dispatchEvent(new CustomEvent("unit-change", {
4948
5023
  detail: {
@@ -4977,6 +5052,14 @@ let JupiterFormField = class extends LitElement {
4977
5052
  _isRoundingLevelConcept() {
4978
5053
  return !!this.conceptId && this.conceptId.includes("DocumentIntendedRoundingLevel");
4979
5054
  }
5055
+ // jenv-bw2-i:BalanceSheetBeforeAfterAppropriationResults records the profit-appropriation
5056
+ // decision for the current balance sheet only — the NL taxonomy's validation rules warn when
5057
+ // it's also reported for the comparative year, so the prior-year column doesn't accept it.
5058
+ // `columnId` carries the `_prev` marker `generatePreviousYearColumns` (xbrl-form-builder.ts)
5059
+ // gives every comparative-year column, whether dimensioned (`${id}_prev`) or not (`duration_prev_N`).
5060
+ _isDisabledForPriorYear() {
5061
+ return !!this.conceptId && !!this.columnId && this.conceptId.includes("BalanceSheetBeforeAfterAppropriationResults") && this.columnId.includes("_prev");
5062
+ }
4980
5063
  /**
4981
5064
  * JDF-110: lock icon (SVG, not emoji) shown on a masterData-prefilled or rounding-level-locked
4982
5065
  * cell, so a locked value reads as "filled and protected" rather than empty placeholder text.
@@ -5620,7 +5703,7 @@ let JupiterFormField = class extends LitElement {
5620
5703
  const isMonetary = this._isMonetaryType();
5621
5704
  return html`<span class="readonly-value ${isEmpty ? "empty" : ""} ${isMonetary ? "monetary" : ""}">${isEmpty ? "—" : displayValue}</span>`;
5622
5705
  }
5623
- _renderInput(effectiveValue = this.value, effectiveDisabled = this.disabled, isLocked = false) {
5706
+ _renderInput(effectiveValue = this.value, effectiveDisabled = this.disabled, isLocked = false, disabledTooltip) {
5624
5707
  if (this.mode === "readonly") {
5625
5708
  return this._renderReadonlyValue(effectiveValue);
5626
5709
  }
@@ -5646,6 +5729,7 @@ let JupiterFormField = class extends LitElement {
5646
5729
  class="${cssClass}"
5647
5730
  .value="${effectiveValue ?? ""}"
5648
5731
  ?disabled="${effectiveDisabled || this.field.disabled}"
5732
+ title="${disabledTooltip || ""}"
5649
5733
  @change="${this._handleInput}"
5650
5734
  @focus="${this._handleFocus}"
5651
5735
  @blur="${this._handleBlur}"
@@ -5997,6 +6081,7 @@ let JupiterFormField = class extends LitElement {
5997
6081
  const baseConceptId = this._extractBaseConceptId(this.conceptId);
5998
6082
  const isPredefinedValue = this.mode !== "admin" && this.masterData && baseConceptId in this.masterData;
5999
6083
  const isRoundingLevelLocked = this._isRoundingLevelConcept();
6084
+ const isDisabledForPriorYear = this._isDisabledForPriorYear();
6000
6085
  let factValue = null;
6001
6086
  if (this.facts && this.facts.length > 0 && !isPredefinedValue) {
6002
6087
  const cellContext = {
@@ -6050,8 +6135,8 @@ let JupiterFormField = class extends LitElement {
6050
6135
  }
6051
6136
  }
6052
6137
  const hasUserValue = this.value !== null && this.value !== void 0;
6053
- const effectiveValue = isPredefinedValue ? this.masterData[baseConceptId] : hasUserValue ? this.value : factValue;
6054
- const effectiveDisabled = isPredefinedValue || isRoundingLevelLocked || this.disabled || this.mode === "readonly";
6138
+ const effectiveValue = isDisabledForPriorYear ? null : isPredefinedValue ? this.masterData[baseConceptId] : hasUserValue ? this.value : factValue;
6139
+ const effectiveDisabled = isPredefinedValue || isRoundingLevelLocked || isDisabledForPriorYear || this.disabled || this.mode === "readonly";
6055
6140
  const isLocked = (isPredefinedValue || isRoundingLevelLocked) && this.mode !== "readonly";
6056
6141
  const typeConfigForLock = getInputTypeForConceptType(this.conceptType, this.datatypes);
6057
6142
  const effectiveFieldTypeForLock = typeConfigForLock.fieldType || this.field.type || "text";
@@ -6065,7 +6150,7 @@ let JupiterFormField = class extends LitElement {
6065
6150
  ` : ""}
6066
6151
 
6067
6152
  <div class="field-wrapper">
6068
- ${this._renderInput(effectiveValue, effectiveDisabled, isLocked)}
6153
+ ${this._renderInput(effectiveValue, effectiveDisabled, isLocked, isDisabledForPriorYear ? I18n.t("field.notApplicablePriorYear") : void 0)}
6069
6154
 
6070
6155
  ${isLocked && !isTextareaField || hasPeriodControl && this.mode !== "readonly" && !isRoundingLevelLocked ? html`
6071
6156
  <div class="field-trailing-icons">
@@ -6992,6 +7077,19 @@ let JupiterConceptTree = class extends LitElement {
6992
7077
  `;
6993
7078
  document.head.appendChild(style);
6994
7079
  }
7080
+ /**
7081
+ * Dispatches the clicked cell's duplicate-fact mismatch up to `jupiter-dynamic-form`, which owns
7082
+ * the click-to-navigate machinery (role/column reveal, side-panel switch, highlight) already
7083
+ * built for the Validate pre-flight popup — reused here to show this one mismatch's facts.
7084
+ */
7085
+ _handleDuplicateFactWarningBadgeClick(e2, mismatch) {
7086
+ e2.stopPropagation();
7087
+ this.dispatchEvent(new CustomEvent("duplicate-fact-warning-click", {
7088
+ detail: { mismatch },
7089
+ bubbles: true,
7090
+ composed: true
7091
+ }));
7092
+ }
6995
7093
  _openCalculationWarningDialog(e2, mismatch) {
6996
7094
  var _a;
6997
7095
  e2.stopPropagation();
@@ -7066,7 +7164,7 @@ let JupiterConceptTree = class extends LitElement {
7066
7164
 
7067
7165
  <!-- Input Field Cells (Period Columns) - Only for non-abstract concepts -->
7068
7166
  ${this.columns.map((column2) => {
7069
- var _a, _b, _c, _d, _e;
7167
+ var _a, _b, _c, _d, _e, _f;
7070
7168
  if (column2.hidden) {
7071
7169
  return html`<td class="field-cell hidden-column"></td>`;
7072
7170
  }
@@ -7075,8 +7173,9 @@ let JupiterConceptTree = class extends LitElement {
7075
7173
  const storedUnit = (_b = (_a = this.unitData) == null ? void 0 : _a[this.concept.id]) == null ? void 0 : _b[column2.id];
7076
7174
  const storedDecimals = (_d = (_c = this.decimalsData) == null ? void 0 : _c[this.concept.id]) == null ? void 0 : _d[column2.id];
7077
7175
  const calcMismatch = this.mode !== "readonly" ? (_e = this.calculationMismatches) == null ? void 0 : _e.get(`${this.concept.id}__${column2.id}`) : void 0;
7176
+ const dupWarning = this.mode !== "readonly" ? (_f = this.duplicateFactWarnings) == null ? void 0 : _f.get(`${this.concept.id}__${column2.id}`) : void 0;
7078
7177
  return html`
7079
- <td class="field-cell ${!shouldShowField ? "empty" : ""} ${isAbstract ? "abstract-row" : ""} ${isTotal ? "total-row" : ""} ${this.highlightType && column2.id === this.highlightColumnId ? "highlight-" + this.highlightType : ""} ${calcMismatch ? "calc-warning" : ""} ${this.rowFocused ? "row-focused" : ""}">
7178
+ <td class="field-cell ${!shouldShowField ? "empty" : ""} ${isAbstract ? "abstract-row" : ""} ${isTotal ? "total-row" : ""} ${this.highlightType && column2.id === this.highlightColumnId ? "highlight-" + this.highlightType : ""} ${calcMismatch ? "calc-warning" : ""} ${dupWarning ? "duplicate-fact-warning" : ""} ${this.rowFocused ? "row-focused" : ""}">
7080
7179
  ${calcMismatch ? html`
7081
7180
  <button
7082
7181
  class="calc-warning-badge"
@@ -7086,6 +7185,15 @@ let JupiterConceptTree = class extends LitElement {
7086
7185
  @click="${(e2) => this._openCalculationWarningDialog(e2, calcMismatch)}"
7087
7186
  >!</button>
7088
7187
  ` : ""}
7188
+ ${dupWarning ? html`
7189
+ <button
7190
+ class="duplicate-warning-badge"
7191
+ type="button"
7192
+ tabindex="-1"
7193
+ title="${I18n.t("duplicateFactWarning.cellTooltip")}"
7194
+ @click="${(e2) => this._handleDuplicateFactWarningBadgeClick(e2, dupWarning)}"
7195
+ >≠</button>
7196
+ ` : ""}
7089
7197
  ${shouldShowField ? html`
7090
7198
  <jupiter-form-field
7091
7199
  .field="${field2}"
@@ -7338,6 +7446,38 @@ JupiterConceptTree.styles = css`
7338
7446
  background: #e65100;
7339
7447
  }
7340
7448
 
7449
+ /* Live counterpart to the Validate pre-flight's duplicate-fact-value check — deliberately a
7450
+ distinct color from calc-warning's amber so the two non-blocking warnings never look like
7451
+ the same issue when both land on the same cell. */
7452
+ .field-cell.duplicate-fact-warning {
7453
+ box-shadow: inset 0 0 0 2px var(--jupiter-duplicate-warning-color, #8e24aa);
7454
+ }
7455
+
7456
+ .duplicate-warning-badge {
7457
+ position: absolute;
7458
+ top: 2px;
7459
+ left: 2px;
7460
+ width: 16px;
7461
+ height: 16px;
7462
+ border-radius: 50%;
7463
+ border: none;
7464
+ background: var(--jupiter-duplicate-warning-color, #8e24aa);
7465
+ color: #fff;
7466
+ font-size: 10px;
7467
+ font-weight: 700;
7468
+ line-height: 1;
7469
+ padding: 0;
7470
+ display: flex;
7471
+ align-items: center;
7472
+ justify-content: center;
7473
+ cursor: pointer;
7474
+ z-index: 3;
7475
+ }
7476
+
7477
+ .duplicate-warning-badge:hover {
7478
+ background: #6a1b9a;
7479
+ }
7480
+
7341
7481
  /* Row-level highlight while a field in this row has focus. Applied to every
7342
7482
  cell so the row stays identifiable even after the user scrolls the table
7343
7483
  horizontally away from the focused input. */
@@ -7491,6 +7631,9 @@ __decorateClass$7([
7491
7631
  __decorateClass$7([
7492
7632
  n2({ type: Object })
7493
7633
  ], JupiterConceptTree.prototype, "calculationMismatches", 2);
7634
+ __decorateClass$7([
7635
+ n2({ type: Object })
7636
+ ], JupiterConceptTree.prototype, "duplicateFactWarnings", 2);
7494
7637
  __decorateClass$7([
7495
7638
  n2({ type: String })
7496
7639
  ], JupiterConceptTree.prototype, "language", 2);
@@ -9278,6 +9421,7 @@ let JupiterFormSection = class extends LitElement {
9278
9421
  .highlightColumnId="${(_b = this._highlightMap.get(instanceConcept.id)) == null ? void 0 : _b.columnId}"
9279
9422
  .rowFocused="${this._focusedConceptId === instanceConcept.id}"
9280
9423
  .calculationMismatches="${this._calculationMismatches}"
9424
+ .duplicateFactWarnings="${this.duplicateFactWarnings}"
9281
9425
  @field-change="${this._handleFieldChange}"
9282
9426
  @field-focus="${this._handleFieldFocusForHighlight}"
9283
9427
  @period-change="${this._handlePeriodChange}"
@@ -9913,6 +10057,9 @@ __decorateClass$5([
9913
10057
  __decorateClass$5([
9914
10058
  n2({ type: Object })
9915
10059
  ], JupiterFormSection.prototype, "totalManuallyEditedData", 2);
10060
+ __decorateClass$5([
10061
+ n2({ type: Object })
10062
+ ], JupiterFormSection.prototype, "duplicateFactWarnings", 2);
9916
10063
  __decorateClass$5([
9917
10064
  r()
9918
10065
  ], JupiterFormSection.prototype, "_expanded", 2);
@@ -12233,6 +12380,44 @@ let JupiterFormulaValidationDialog = class extends LitElement {
12233
12380
  composed: true
12234
12381
  }));
12235
12382
  }
12383
+ // JDF-129: same navigate-and-close flow as `_handleConceptLinkClick`, for a breakdown line
12384
+ // rather than a message's backtick-quoted concept mention.
12385
+ _handleBreakdownCandidateClick(candidate) {
12386
+ this.dispatchEvent(new CustomEvent("formula-concept-click", {
12387
+ detail: { conceptId: candidate.conceptId, conceptQName: candidate.conceptQName, columnId: candidate.columnId },
12388
+ bubbles: true,
12389
+ composed: true
12390
+ }));
12391
+ }
12392
+ // JDF-129: local (non-prefixed) name for a breakdown candidate's concept — same derivation as
12393
+ // `_buildConceptLinks`'s own label in FormulaValidationService.
12394
+ _breakdownCandidateLabel(candidate) {
12395
+ return candidate.conceptQName.includes(":") ? candidate.conceptQName.split(":").pop() : candidate.conceptQName;
12396
+ }
12397
+ // JDF-129: plain, unflagged per-candidate breakdown for a bindAsSequence:true variable — the
12398
+ // real facts an aggregate sum/comparison summed, so a user can see one corrected line reflected
12399
+ // even though the assertion as a whole (a pure aggregate check) is still failing. Deliberately no
12400
+ // per-line pass/fail — see the ticket's own "Diff behavior" scoping decision.
12401
+ _renderBreakdown(result) {
12402
+ const breakdown = result.variableBreakdown;
12403
+ if (!breakdown || breakdown.length === 0)
12404
+ return "";
12405
+ return html`
12406
+ <div class="assertion-breakdown">
12407
+ <span class="assertion-breakdown-label">${I18n.t("formulaValidation.breakdownLabel")}</span>
12408
+ <ul class="assertion-breakdown-list">
12409
+ ${breakdown.flatMap((variable) => variable.candidates.map((candidate) => html`
12410
+ <li class="assertion-breakdown-item">
12411
+ <button type="button" class="concept-link" @click="${() => this._handleBreakdownCandidateClick(candidate)}">
12412
+ ${this._breakdownCandidateLabel(candidate)}
12413
+ </button>
12414
+ <span class="assertion-breakdown-value">${candidate.value}</span>
12415
+ </li>
12416
+ `))}
12417
+ </ul>
12418
+ </div>
12419
+ `;
12420
+ }
12236
12421
  // Splits a message on its backtick-quoted concept mentions and renders the ones this
12237
12422
  // assertion resolved a fact location for (`result.conceptLinks`) as click-to-focus buttons;
12238
12423
  // any other backtick mention (e.g. an enumerated value like `Na`, not a concept) renders as
@@ -12299,7 +12484,10 @@ let JupiterFormulaValidationDialog = class extends LitElement {
12299
12484
  <span class="severity-badge ${result.severity === "ERROR" ? "error" : "warning"}">
12300
12485
  ${result.severity === "ERROR" ? I18n.t("formulaValidation.severityError") : I18n.t("formulaValidation.severityWarning")}
12301
12486
  </span>
12302
- <span class="assertion-message">${this._renderMessage(result)}</span>
12487
+ <div class="assertion-row-body">
12488
+ <span class="assertion-message">${this._renderMessage(result)}</span>
12489
+ ${this._renderBreakdown(result)}
12490
+ </div>
12303
12491
  </div>
12304
12492
  `)}
12305
12493
  </div>
@@ -12488,12 +12676,53 @@ JupiterFormulaValidationDialog.styles = css`
12488
12676
  background: var(--jupiter-error-color, #d32f2f);
12489
12677
  }
12490
12678
 
12679
+ .assertion-row-body {
12680
+ display: flex;
12681
+ flex-direction: column;
12682
+ gap: 4px;
12683
+ min-width: 0;
12684
+ }
12685
+
12491
12686
  .assertion-message {
12492
12687
  font-size: 13px;
12493
12688
  color: var(--primaryTextColor, var(--jupiter-text-primary, #333));
12494
12689
  line-height: 1.4;
12495
12690
  }
12496
12691
 
12692
+ .assertion-breakdown {
12693
+ display: flex;
12694
+ flex-direction: column;
12695
+ gap: 2px;
12696
+ }
12697
+
12698
+ .assertion-breakdown-label {
12699
+ font-size: 11px;
12700
+ font-weight: 600;
12701
+ color: var(--jupiter-text-secondary, #666);
12702
+ }
12703
+
12704
+ .assertion-breakdown-list {
12705
+ list-style: none;
12706
+ margin: 0;
12707
+ padding: 0;
12708
+ display: flex;
12709
+ flex-direction: column;
12710
+ gap: 2px;
12711
+ }
12712
+
12713
+ .assertion-breakdown-item {
12714
+ display: flex;
12715
+ align-items: baseline;
12716
+ justify-content: space-between;
12717
+ gap: 8px;
12718
+ font-size: 12px;
12719
+ }
12720
+
12721
+ .assertion-breakdown-value {
12722
+ color: var(--jupiter-text-secondary, #666);
12723
+ white-space: nowrap;
12724
+ }
12725
+
12497
12726
  .concept-link {
12498
12727
  font: inherit;
12499
12728
  font-weight: 600;
@@ -12785,8 +13014,11 @@ let JupiterDynamicForm = class extends LitElement {
12785
13014
  this._xbrlFormErrors = [];
12786
13015
  this._calculationWarnings = /* @__PURE__ */ new Map();
12787
13016
  this._showErrorPopup = false;
12788
- this._showTypedDimensionWarning = false;
13017
+ this._showValidateWarningPopup = false;
12789
13018
  this._typedDimensionWarningFacts = [];
13019
+ this._duplicateFactMismatches = [];
13020
+ this._duplicateFactWarnings = /* @__PURE__ */ new Map();
13021
+ this._duplicateWarningPopupMode = "validate";
12790
13022
  this._submitDisabled = false;
12791
13023
  this._allSections = [];
12792
13024
  this._selectedRoleIds = [];
@@ -12844,6 +13076,15 @@ let JupiterDynamicForm = class extends LitElement {
12844
13076
  this.addEventListener("calculation-mismatch-changed", (e2) => {
12845
13077
  this._handleCalculationMismatchChanged(e2);
12846
13078
  });
13079
+ this.addEventListener("duplicate-fact-warning-click", (e2) => {
13080
+ const { mismatch } = e2.detail || {};
13081
+ if (!mismatch)
13082
+ return;
13083
+ this._typedDimensionWarningFacts = [];
13084
+ this._duplicateFactMismatches = [mismatch];
13085
+ this._duplicateWarningPopupMode = "cellInfo";
13086
+ this._showValidateWarningPopup = true;
13087
+ });
12847
13088
  this.addEventListener("total-manually-edited", (e2) => {
12848
13089
  const { conceptId, columnId } = e2.detail;
12849
13090
  const updated = { ...this._totalManuallyEditedData };
@@ -13057,6 +13298,7 @@ let JupiterDynamicForm = class extends LitElement {
13057
13298
  if ((_a = this.xbrlInput) == null ? void 0 : _a.initialData) {
13058
13299
  this._formData = { ...this._formData, ...this.xbrlInput.initialData };
13059
13300
  }
13301
+ this._formData = this._stripPriorYearExcludedFacts(this._formData);
13060
13302
  this._initializeGlobalDecimalsFromRoundingData();
13061
13303
  this._extractTypedMembersFromFacts();
13062
13304
  if (!this._skipDraftLoading) {
@@ -13737,8 +13979,15 @@ let JupiterDynamicForm = class extends LitElement {
13737
13979
  // which reads as "still cut off" when its row height isn't an exact multiple of the container.
13738
13980
  _scrollActiveRoleIntoView() {
13739
13981
  var _a;
13740
- const activeItem = (_a = this.shadowRoot) == null ? void 0 : _a.querySelector(".side-panel-roles-list .side-panel-role-item.active");
13741
- activeItem == null ? void 0 : activeItem.scrollIntoView({ block: "center" });
13982
+ const rolesList = (_a = this.shadowRoot) == null ? void 0 : _a.querySelector(".side-panel-roles-list");
13983
+ const activeItem = rolesList == null ? void 0 : rolesList.querySelector(".side-panel-role-item.active");
13984
+ if (!rolesList || !activeItem)
13985
+ return;
13986
+ const listRect = rolesList.getBoundingClientRect();
13987
+ const itemRect = activeItem.getBoundingClientRect();
13988
+ const itemCenter = itemRect.top + itemRect.height / 2;
13989
+ const listCenter = listRect.top + listRect.height / 2;
13990
+ rolesList.scrollTop += itemCenter - listCenter;
13742
13991
  }
13743
13992
  // Re-locates the fact input identified by _lastFocusedFieldIdentity after a rebuild and restores
13744
13993
  // focus to it, mirroring the shadow-DOM traversal scrollToConcept/_handleErrorFieldClick use.
@@ -13963,6 +14212,29 @@ let JupiterDynamicForm = class extends LitElement {
13963
14212
  this._calculationWarnings = next;
13964
14213
  this._validateForm();
13965
14214
  }
14215
+ // jenv-bw2-i:BalanceSheetBeforeAfterAppropriationResults isn't applicable to the previous-year
14216
+ // column — mirrors `_isDisabledForPriorYear` in form-field.ts (which disables the input itself);
14217
+ // this is the `_formData`/submission-side counterpart so a value already sitting in a draft,
14218
+ // `initialData`, or preserved (hidden-role) data from before that UI restriction existed gets
14219
+ // scrubbed on load and can never resurface in a later draft save or submission.
14220
+ _isPriorYearExcludedFact(conceptId, columnId) {
14221
+ return !!conceptId && !!columnId && conceptId.includes("BalanceSheetBeforeAfterAppropriationResults") && columnId.includes("_prev");
14222
+ }
14223
+ /** Strips any prior-year-excluded concept/column entries from a `FormData` map (immutable). */
14224
+ _stripPriorYearExcludedFacts(data) {
14225
+ const cleaned = {};
14226
+ Object.keys(data).forEach((conceptId) => {
14227
+ const columns = data[conceptId] || {};
14228
+ const keptColumns = {};
14229
+ Object.keys(columns).forEach((columnId) => {
14230
+ if (!this._isPriorYearExcludedFact(conceptId, columnId)) {
14231
+ keptColumns[columnId] = columns[columnId];
14232
+ }
14233
+ });
14234
+ cleaned[conceptId] = keptColumns;
14235
+ });
14236
+ return cleaned;
14237
+ }
13966
14238
  /**
13967
14239
  * JDF-058: single source of truth for writing a value into `_formData` with the immutable-update
13968
14240
  * pattern Lit reactivity needs — shared by a real user keystroke (`_handleFieldChange`) and the
@@ -13970,6 +14242,10 @@ let JupiterDynamicForm = class extends LitElement {
13970
14242
  * duplicated. Returns the value that was previously stored, for event `oldValue` fields.
13971
14243
  */
13972
14244
  _writeFormDataValue(conceptId, columnId, value) {
14245
+ var _a;
14246
+ if (this._isPriorYearExcludedFact(conceptId, columnId)) {
14247
+ return (_a = this._formData[conceptId]) == null ? void 0 : _a[columnId];
14248
+ }
13973
14249
  const updatedFormData = { ...this._formData };
13974
14250
  if (!updatedFormData[conceptId]) {
13975
14251
  updatedFormData[conceptId] = {};
@@ -14022,13 +14298,17 @@ let JupiterDynamicForm = class extends LitElement {
14022
14298
  this.requestUpdate();
14023
14299
  }
14024
14300
  _handleUnitChange(event) {
14301
+ var _a;
14025
14302
  console.log(`🏷️ [DynamicForm] _handleUnitChange called with event:`, event);
14026
14303
  console.log(`🏷️ [DynamicForm] Event detail:`, event.detail);
14027
14304
  const { conceptId, columnId, unit } = event.detail;
14028
14305
  console.log(`🏷️ Unit change received: conceptId=${conceptId}, columnId=${columnId}, unit=${unit}`);
14306
+ const isUnchanged = ((_a = this._unitData[conceptId]) == null ? void 0 : _a[columnId]) === unit;
14029
14307
  this._storeUnit(conceptId, columnId, unit);
14030
- this._dirty = true;
14031
- this.hasUnsavedChanges = true;
14308
+ if (!isUnchanged) {
14309
+ this._dirty = true;
14310
+ this.hasUnsavedChanges = true;
14311
+ }
14032
14312
  this.requestUpdate();
14033
14313
  }
14034
14314
  _storeUnit(conceptId, columnId, unit) {
@@ -14336,6 +14616,7 @@ let JupiterDynamicForm = class extends LitElement {
14336
14616
  console.log(`🟧 [DynamicForm] Removed ${beforeLength - this._xbrlFormErrors.length} errors`);
14337
14617
  }
14338
14618
  console.log(`🔍 [Validation] Total errors: ${this._xbrlFormErrors.length}`, this._xbrlFormErrors);
14619
+ this._updateDuplicateFactWarnings(conceptId, columnId);
14339
14620
  }
14340
14621
  _getSectionTitle(sectionId) {
14341
14622
  console.log(`🔍 [_getSectionTitle] Looking for sectionId: "${sectionId}"`);
@@ -14354,7 +14635,12 @@ let JupiterDynamicForm = class extends LitElement {
14354
14635
  await this._focusFormField(conceptId, columnId, sectionId);
14355
14636
  }
14356
14637
  async _handleTypedDimensionFactClick(conceptId, columnId, sectionId) {
14357
- this._showTypedDimensionWarning = false;
14638
+ this._showValidateWarningPopup = false;
14639
+ this.requestUpdate();
14640
+ await this._focusFormField(conceptId, columnId, sectionId);
14641
+ }
14642
+ async _handleDuplicateFactClick(conceptId, columnId, sectionId) {
14643
+ this._showValidateWarningPopup = false;
14358
14644
  this.requestUpdate();
14359
14645
  await this._focusFormField(conceptId, columnId, sectionId);
14360
14646
  }
@@ -15047,6 +15333,164 @@ let JupiterDynamicForm = class extends LitElement {
15047
15333
  }
15048
15334
  return groups;
15049
15335
  }
15336
+ /**
15337
+ * Validate pre-flight: finds entered facts that share their concept, period and dimensions
15338
+ * (i.e. would resolve to the same XBRL context) with at least one other entered fact — typically
15339
+ * because the same concept is presented in more than one disclosure role — but were given
15340
+ * different values. E.g. `IncomeTaxExpense` entered as 992 in one note and -992 in another for
15341
+ * the same year: only one can be correct, so this is a genuine conflict rather than a benign
15342
+ * duplicate (an identical value entered twice in two roles is left alone — see
15343
+ * `_normalizeFactValueForCompare` below). Walks every concept's own `fields` (not every
15344
+ * column x every concept) to stay cheap on large taxonomies.
15345
+ */
15346
+ _findDuplicateFactMismatches() {
15347
+ const mismatches = [];
15348
+ if (!this._currentSchema)
15349
+ return mismatches;
15350
+ const groups = /* @__PURE__ */ new Map();
15351
+ for (const section2 of this._currentSchema.sections) {
15352
+ const columnsById = new Map((section2.columns || []).map((col) => [col.id, col]));
15353
+ this._collectDuplicateFactCandidates(section2.concepts, columnsById, section2, groups);
15354
+ }
15355
+ groups.forEach((group) => {
15356
+ if (group.entries.length < 2)
15357
+ return;
15358
+ const distinctValues = new Set(group.entries.map((e2) => this._normalizeFactValueForCompare(e2.value)));
15359
+ if (distinctValues.size < 2)
15360
+ return;
15361
+ mismatches.push({
15362
+ conceptId: group.conceptId,
15363
+ conceptLabel: group.conceptLabel,
15364
+ facts: group.entries
15365
+ });
15366
+ });
15367
+ return mismatches;
15368
+ }
15369
+ _collectDuplicateFactCandidates(concepts, columnsById, section2, groups) {
15370
+ var _a;
15371
+ for (const concept of concepts) {
15372
+ const conceptValues = this._formData[concept.id];
15373
+ if (conceptValues) {
15374
+ for (const field2 of concept.fields) {
15375
+ const value = conceptValues[field2.columnId];
15376
+ if (value === void 0 || value === null || String(value).trim() === "")
15377
+ continue;
15378
+ const column2 = columnsById.get(field2.columnId);
15379
+ if (!column2)
15380
+ continue;
15381
+ const contextKey = this._buildFactContextKey(concept, field2, column2, section2);
15382
+ if (!contextKey)
15383
+ continue;
15384
+ const entry = {
15385
+ conceptId: concept.id,
15386
+ conceptLabel: concept.label,
15387
+ columnId: column2.id,
15388
+ columnTitle: column2.title,
15389
+ sectionId: section2.id,
15390
+ sectionTitle: section2.title,
15391
+ value
15392
+ };
15393
+ let group = groups.get(contextKey);
15394
+ if (!group) {
15395
+ group = { conceptId: concept.originalConceptId || concept.id, conceptLabel: concept.label, entries: [] };
15396
+ groups.set(contextKey, group);
15397
+ }
15398
+ group.entries.push(entry);
15399
+ }
15400
+ }
15401
+ if ((_a = concept.children) == null ? void 0 : _a.length) {
15402
+ this._collectDuplicateFactCandidates(concept.children, columnsById, section2, groups);
15403
+ }
15404
+ }
15405
+ }
15406
+ /**
15407
+ * Canonical key for the XBRL context (concept + period + dimensions) `concept`'s value in
15408
+ * `column` would resolve to, independent of which role produced it. Returns null when a typed
15409
+ * dimension on this column has no value entered yet, since its true context can't be
15410
+ * determined without one (that gap is already surfaced by the typed-dimension warning).
15411
+ */
15412
+ _buildFactContextKey(concept, field2, column2, section2) {
15413
+ var _a, _b, _c;
15414
+ const conceptKey = concept.originalConceptId || concept.id;
15415
+ const isInstant = concept.periodType === "instant";
15416
+ const fieldPeriodData = (_a = this._periodData[concept.id]) == null ? void 0 : _a[column2.id];
15417
+ const periodStartDate = (fieldPeriodData == null ? void 0 : fieldPeriodData.startDate) || column2.periodStartDate || field2.periodStartDate || this.periodStartDate;
15418
+ const periodEndDate = (fieldPeriodData == null ? void 0 : fieldPeriodData.endDate) || column2.periodEndDate || field2.periodEndDate || this.periodEndDate;
15419
+ const periodInstantDate = (fieldPeriodData == null ? void 0 : fieldPeriodData.instantDate) || field2.periodInstantDate || periodEndDate || periodStartDate;
15420
+ const periodKey = isInstant ? `instant:${periodInstantDate}` : `duration:${periodStartDate}_${periodEndDate}`;
15421
+ const dimensionData = column2.dimensionData;
15422
+ let dimensionKey = "base";
15423
+ if (dimensionData) {
15424
+ const typedMembers = dimensionData.typedMembers;
15425
+ if (typedMembers && typedMembers.length > 0) {
15426
+ const scoped = this._typedMemberData[this._scopedColumnKey(section2.id, column2.id)];
15427
+ const legacy = this._typedMemberData[column2.id];
15428
+ const typedParts = [];
15429
+ for (const tm of typedMembers) {
15430
+ const typedValue = (_b = (scoped == null ? void 0 : scoped[tm.axisId]) ?? (legacy == null ? void 0 : legacy[tm.axisId])) == null ? void 0 : _b.trim();
15431
+ if (!typedValue)
15432
+ return null;
15433
+ typedParts.push(`${tm.axisId}=${typedValue}`);
15434
+ }
15435
+ dimensionKey = typedParts.sort().join("|");
15436
+ } else if ((_c = dimensionData.combinations) == null ? void 0 : _c.length) {
15437
+ dimensionKey = dimensionData.combinations.map((c2) => `${c2.axisId}=${c2.memberId}`).sort().join("|");
15438
+ } else if (dimensionData.dimensionIdKey) {
15439
+ dimensionKey = dimensionData.dimensionIdKey;
15440
+ } else if (dimensionData.axisId && dimensionData.memberId) {
15441
+ dimensionKey = `${dimensionData.axisId}=${dimensionData.memberId}`;
15442
+ }
15443
+ }
15444
+ return `${conceptKey}||${periodKey}||${dimensionKey}`;
15445
+ }
15446
+ /** Numeric-aware value comparison for the duplicate-fact check — "992" and 992 (or "992.0") count as equal. */
15447
+ _normalizeFactValueForCompare(value) {
15448
+ if (typeof value === "number")
15449
+ return String(value);
15450
+ if (typeof value === "string") {
15451
+ const trimmed = value.trim();
15452
+ const asNumber = Number(trimmed);
15453
+ return trimmed !== "" && !isNaN(asNumber) ? String(asNumber) : trimmed;
15454
+ }
15455
+ return String(value);
15456
+ }
15457
+ /**
15458
+ * Live, blur-triggered counterpart to `_findDuplicateFactMismatches`. Re-runs the full duplicate
15459
+ * check (cheap — the same pass Validate already does) and rebuilds `_duplicateFactWarnings` from
15460
+ * scratch, so a resolved conflict clears itself and a newly created one appears without any
15461
+ * extra bookkeeping. Every fact in every current mismatch group gets flagged, not just the field
15462
+ * that was just blurred, so the user sees all the cells involved in a conflict — including one
15463
+ * they filled in earlier — the moment either side changes, rather than having to revisit them
15464
+ * one by one after Validate. Skipped when the blurred cell has no value and wasn't already
15465
+ * flagged, since a blank cell can never be part of a mismatch (`_collectDuplicateFactCandidates`
15466
+ * ignores blanks) — this keeps tabbing through empty cells cheap.
15467
+ */
15468
+ _updateDuplicateFactWarnings(conceptId, columnId) {
15469
+ var _a;
15470
+ const value = (_a = this._formData[conceptId]) == null ? void 0 : _a[columnId];
15471
+ const hasMeaningfulValue = value !== void 0 && value !== null && String(value).trim() !== "";
15472
+ const key = `${conceptId}__${columnId}`;
15473
+ if (!hasMeaningfulValue && !this._duplicateFactWarnings.has(key))
15474
+ return;
15475
+ const mismatches = this._findDuplicateFactMismatches();
15476
+ const updated = /* @__PURE__ */ new Map();
15477
+ for (const mismatch of mismatches) {
15478
+ for (const fact of mismatch.facts) {
15479
+ updated.set(`${fact.conceptId}__${fact.columnId}`, mismatch);
15480
+ }
15481
+ }
15482
+ this._duplicateFactWarnings = updated;
15483
+ }
15484
+ /** Popup header for the Validate pre-flight warning: specific when only one check found something, generic when both did. */
15485
+ _validateWarningPopupTitle() {
15486
+ const hasTypedDimensionWarning = this._typedDimensionWarningFacts.length > 0;
15487
+ const hasDuplicateFactWarning = this._duplicateFactMismatches.length > 0;
15488
+ if (hasTypedDimensionWarning && hasDuplicateFactWarning)
15489
+ return I18n.t("validateWarningPopup.title");
15490
+ if (hasDuplicateFactWarning)
15491
+ return I18n.t("duplicateFactWarning.title");
15492
+ return I18n.t("typedDimensionWarning.title");
15493
+ }
15050
15494
  _handleSubmit() {
15051
15495
  console.log("📝 Form submission started...");
15052
15496
  this._submitted = true;
@@ -15058,10 +15502,14 @@ let JupiterDynamicForm = class extends LitElement {
15058
15502
  return;
15059
15503
  }
15060
15504
  const missingTypedDimensionFacts = this._findMissingTypedDimensionFacts();
15061
- if (missingTypedDimensionFacts.length > 0) {
15505
+ const duplicateFactMismatches = this._findDuplicateFactMismatches();
15506
+ if (missingTypedDimensionFacts.length > 0 || duplicateFactMismatches.length > 0) {
15062
15507
  console.log("⚠️ [Submit] Typed dimension values missing for entered facts:", missingTypedDimensionFacts);
15508
+ console.log("⚠️ [Submit] Duplicate facts with mismatched values:", duplicateFactMismatches);
15063
15509
  this._typedDimensionWarningFacts = missingTypedDimensionFacts;
15064
- this._showTypedDimensionWarning = true;
15510
+ this._duplicateFactMismatches = duplicateFactMismatches;
15511
+ this._duplicateWarningPopupMode = "validate";
15512
+ this._showValidateWarningPopup = true;
15065
15513
  this._submitDisabled = false;
15066
15514
  this.requestUpdate();
15067
15515
  return;
@@ -15070,8 +15518,8 @@ let JupiterDynamicForm = class extends LitElement {
15070
15518
  }
15071
15519
  /**
15072
15520
  * The original Validate/submit logic (error checks + submission), extracted so "Continue to
15073
- * validate" on the typed-dimension warning popup can re-enter it directly without re-running
15074
- * (and re-triggering) the typed-dimension pre-flight in _handleSubmit() above.
15521
+ * validate" on the Validate pre-flight warning popup can re-enter it directly without
15522
+ * re-running (and re-triggering) the checks in _handleSubmit() above.
15075
15523
  */
15076
15524
  _continueSubmit() {
15077
15525
  this._submitted = true;
@@ -15431,6 +15879,10 @@ let JupiterDynamicForm = class extends LitElement {
15431
15879
  return;
15432
15880
  }
15433
15881
  }
15882
+ if (this._isPriorYearExcludedFact(conceptId, columnId)) {
15883
+ console.log(`⏭️ Dropping legacy draft value for ${conceptId}/${columnId} — not applicable to the previous year`);
15884
+ return;
15885
+ }
15434
15886
  const instanceKey = draftInstanceId || conceptId;
15435
15887
  const baseConceptId = instanceKey.includes("__repeat_") ? instanceKey.split("__repeat_")[0] : instanceKey;
15436
15888
  const actualBaseId = this._findActualConceptId(baseConceptId);
@@ -15974,6 +16426,8 @@ let JupiterDynamicForm = class extends LitElement {
15974
16426
  }
15975
16427
  _addConceptDataToSubmission(concept, columnId, value, submissionData, section2) {
15976
16428
  var _a, _b, _c, _d, _e;
16429
+ if (this._isPriorYearExcludedFact(concept.id, columnId))
16430
+ return;
15977
16431
  const field2 = concept.fields.find((f2) => f2.columnId === columnId);
15978
16432
  if (!field2)
15979
16433
  return;
@@ -16146,26 +16600,25 @@ let JupiterDynamicForm = class extends LitElement {
16146
16600
  }
16147
16601
  if (concept.fields && concept.fields.length > 0) {
16148
16602
  concept.fields.forEach((field2) => {
16149
- var _a, _b, _c, _d, _e, _f, _g, _h;
16603
+ var _a, _b, _c, _d, _e, _f, _g;
16604
+ if (this._isPriorYearExcludedFact(concept.id, field2.columnId)) {
16605
+ return;
16606
+ }
16150
16607
  const conceptData = this._formData[concept.id];
16151
- let fieldValue = conceptData == null ? void 0 : conceptData[field2.columnId];
16152
16608
  const baseConceptId = field2.conceptId || concept.id.split("__").slice(0, -1).join("__") || concept.id;
16153
- if ((fieldValue === void 0 || fieldValue === null || fieldValue === "") && this._isRoundingLevelConcept(baseConceptId) && !this._hasValidGlobalDecimals()) {
16609
+ const masterDataWins = this.mode !== "admin" && !!this._effectiveMasterData && baseConceptId in this._effectiveMasterData;
16610
+ let fieldValue = masterDataWins ? this._effectiveMasterData[baseConceptId] : conceptData == null ? void 0 : conceptData[field2.columnId];
16611
+ if (masterDataWins) {
16612
+ console.log(` 📦 [Submission] Using masterData for: ${baseConceptId} [${field2.columnId}] = ${JSON.stringify(fieldValue)}`);
16613
+ } else if ((fieldValue === void 0 || fieldValue === null || fieldValue === "") && this._isRoundingLevelConcept(baseConceptId) && !this._hasValidGlobalDecimals()) {
16154
16614
  const factValue = this._findFactValueForField(concept, field2, section2);
16155
16615
  if (factValue !== void 0 && factValue !== null && factValue !== "") {
16156
16616
  fieldValue = factValue;
16157
16617
  }
16158
16618
  }
16159
- if ((fieldValue === void 0 || fieldValue === null || fieldValue === "") && this._effectiveMasterData) {
16160
- const masterValue = (_a = this._effectiveMasterData) == null ? void 0 : _a[baseConceptId];
16161
- if (masterValue !== void 0 && masterValue !== null && masterValue !== "") {
16162
- fieldValue = masterValue;
16163
- console.log(` 📦 [Submission] Using masterData for: ${baseConceptId} [${field2.columnId}] = ${JSON.stringify(fieldValue)}`);
16164
- }
16165
- }
16166
16619
  if (fieldValue !== void 0 && fieldValue !== null && fieldValue !== "") {
16167
16620
  const column2 = this._findColumnByIdInSection(field2.columnId, section2);
16168
- const fieldPeriodData = (_b = this._periodData[concept.id]) == null ? void 0 : _b[field2.columnId];
16621
+ const fieldPeriodData = (_a = this._periodData[concept.id]) == null ? void 0 : _a[field2.columnId];
16169
16622
  const submissionEntry = {
16170
16623
  conceptId: concept.id,
16171
16624
  draftInstanceId: concept.id,
@@ -16187,21 +16640,21 @@ let JupiterDynamicForm = class extends LitElement {
16187
16640
  submissionEntry.period.endDate = endDate;
16188
16641
  }
16189
16642
  console.log(`🔍 [Submission] Concept: ${concept.id}, Column: ${field2.columnId}, Field Period: ${fieldPeriodData ? JSON.stringify(fieldPeriodData) : "none"}, Column Period: ${(column2 == null ? void 0 : column2.periodStartDate) || "none"} - ${(column2 == null ? void 0 : column2.periodEndDate) || "none"}, Used Period: ${submissionEntry.period.type === "instant" ? submissionEntry.period.date : `${submissionEntry.period.startDate} - ${submissionEntry.period.endDate}`}`);
16190
- const fieldUnit = (_c = this._unitData[concept.id]) == null ? void 0 : _c[field2.columnId];
16643
+ const fieldUnit = (_b = this._unitData[concept.id]) == null ? void 0 : _b[field2.columnId];
16191
16644
  if (fieldUnit) {
16192
16645
  submissionEntry.unit = fieldUnit;
16193
16646
  console.log(`✅ [Submission] Adding unit to entry: ${fieldUnit} for ${concept.id}/${field2.columnId}`);
16194
16647
  } else {
16195
16648
  console.log(`⚠️ [Submission] No unit found in _unitData for ${concept.id}/${field2.columnId}. _unitData state:`, JSON.stringify(this._unitData, null, 2));
16196
16649
  }
16197
- const isMonetary = (_d = concept.type) == null ? void 0 : _d.toLowerCase().includes("monetary");
16198
- const fieldDecimals = (_e = this._decimalsData[concept.id]) == null ? void 0 : _e[field2.columnId];
16650
+ const isMonetary = (_c = concept.type) == null ? void 0 : _c.toLowerCase().includes("monetary");
16651
+ const fieldDecimals = (_d = this._decimalsData[concept.id]) == null ? void 0 : _d[field2.columnId];
16199
16652
  const decimalsValue = fieldDecimals || (this.decimals !== "INF" ? this.decimals : void 0);
16200
16653
  if (isMonetary && decimalsValue) {
16201
16654
  const parsed = parseFloat(decimalsValue);
16202
16655
  submissionEntry.decimals = isNaN(parsed) ? decimalsValue : String(-Math.abs(parsed));
16203
16656
  }
16204
- if ((column2 == null ? void 0 : column2.type) === "dimension" && ((_f = column2.dimensionData) == null ? void 0 : _f.dimensionIdKey)) {
16657
+ if ((column2 == null ? void 0 : column2.type) === "dimension" && ((_e = column2.dimensionData) == null ? void 0 : _e.dimensionIdKey)) {
16205
16658
  submissionEntry.dimension = column2.dimensionData.dimensionIdKey;
16206
16659
  console.log(`🔍 [DynamicForm] Using dimension key from field's column (${field2.columnId}):`, column2.dimensionData.dimensionIdKey);
16207
16660
  } else {
@@ -16240,12 +16693,12 @@ let JupiterDynamicForm = class extends LitElement {
16240
16693
  console.log(`🔍 [DynamicForm] Column details:`, {
16241
16694
  id: column2.id,
16242
16695
  type: column2.type,
16243
- hasTypedMembers: (_g = column2.dimensionData) == null ? void 0 : _g.hasTypedMembers,
16696
+ hasTypedMembers: (_f = column2.dimensionData) == null ? void 0 : _f.hasTypedMembers,
16244
16697
  dimensionData: column2.dimensionData
16245
16698
  });
16246
16699
  }
16247
16700
  }
16248
- if (!submissionEntry.typedMembers && ((_h = field2.crossRoleTypedMembers) == null ? void 0 : _h.length)) {
16701
+ if (!submissionEntry.typedMembers && ((_g = field2.crossRoleTypedMembers) == null ? void 0 : _g.length)) {
16249
16702
  const crossRoleKey = `${concept.id}__${field2.columnId}`;
16250
16703
  const crossRoleValues = this._typedMemberData[crossRoleKey];
16251
16704
  if (crossRoleValues) {
@@ -16336,7 +16789,7 @@ let JupiterDynamicForm = class extends LitElement {
16336
16789
  });
16337
16790
  }
16338
16791
  _handleReset() {
16339
- this._formData = { ...this.initialData };
16792
+ this._formData = this._stripPriorYearExcludedFacts(this.initialData);
16340
16793
  this._touched.clear();
16341
16794
  this._dirty = false;
16342
16795
  this.hasUnsavedChanges = false;
@@ -17103,6 +17556,7 @@ let JupiterDynamicForm = class extends LitElement {
17103
17556
  .periodEndDate="${this.periodEndDate}"
17104
17557
  .calculationEnabled="${this.calculationEnabled}"
17105
17558
  .totalManuallyEditedData="${this._totalManuallyEditedData}"
17559
+ .duplicateFactWarnings="${this._duplicateFactWarnings}"
17106
17560
  @field-change="${this._handleFieldChange}"
17107
17561
  @period-change="${this._handlePeriodChange}"
17108
17562
  @typed-member-change="${this._handleTypedMemberChange}"
@@ -17310,6 +17764,7 @@ let JupiterDynamicForm = class extends LitElement {
17310
17764
  .periodEndDate="${this.periodEndDate}"
17311
17765
  .calculationEnabled="${this.calculationEnabled}"
17312
17766
  .totalManuallyEditedData="${this._totalManuallyEditedData}"
17767
+ .duplicateFactWarnings="${this._duplicateFactWarnings}"
17313
17768
  @field-change="${this._handleFieldChange}"
17314
17769
  @typed-member-change="${this._handleTypedMemberChange}"
17315
17770
  @add-concept-repeat="${this._handleAddConceptRepeat}"
@@ -17362,7 +17817,7 @@ let JupiterDynamicForm = class extends LitElement {
17362
17817
  return { ...this._formData };
17363
17818
  }
17364
17819
  setData(data) {
17365
- this._formData = { ...data };
17820
+ this._formData = this._stripPriorYearExcludedFacts(data);
17366
17821
  this._dirty = true;
17367
17822
  this.hasUnsavedChanges = true;
17368
17823
  this._validateForm();
@@ -18098,55 +18553,96 @@ let JupiterDynamicForm = class extends LitElement {
18098
18553
  </div>
18099
18554
  ` : ""}
18100
18555
 
18101
- <!-- Typed Dimension Warning Popup -->
18102
- ${this._showTypedDimensionWarning ? html`
18556
+ <!-- Validate Pre-flight Warning Popup: typed-dimension-missing facts and/or duplicate-fact value mismatches -->
18557
+ ${this._showValidateWarningPopup ? html`
18103
18558
  <div class="error-popup-overlay" @click="${() => {
18104
- this._showTypedDimensionWarning = false;
18559
+ this._showValidateWarningPopup = false;
18105
18560
  this.requestUpdate();
18106
18561
  }}">
18107
18562
  <div class="error-popup" @click="${(e2) => e2.stopPropagation()}">
18108
18563
  <div class="error-popup-header">
18109
- <h3>⚠️ ${I18n.t("typedDimensionWarning.title")}</h3>
18564
+ <h3>⚠️ ${this._validateWarningPopupTitle()}</h3>
18110
18565
  <button class="error-popup-close" @click="${() => {
18111
- this._showTypedDimensionWarning = false;
18566
+ this._showValidateWarningPopup = false;
18112
18567
  this.requestUpdate();
18113
18568
  }}">×</button>
18114
18569
  </div>
18115
18570
  <div class="error-popup-content">
18116
- <p>${I18n.t("typedDimensionWarning.message")}</p>
18117
- <ul class="error-list">
18118
- ${this._groupTypedDimensionWarningFactsByRole().map((group) => html`
18119
- <li>
18120
- <strong>${I18n.t("typedDimensionWarning.role")}</strong> ${group.sectionTitle}
18121
- <ul>
18122
- ${group.facts.map((fact) => html`
18123
- <li>
18124
- <strong>${I18n.t("typedDimensionWarning.field")}</strong>
18125
- <a
18126
- href="javascript:void(0)"
18127
- class="error-field-link"
18128
- @click="${() => this._handleTypedDimensionFactClick(fact.conceptId, fact.columnId, fact.sectionId)}"
18129
- title="${I18n.t("typedDimensionWarning.clickToFocus")}"
18130
- >
18131
- ${fact.conceptLabel} (${fact.columnTitle})
18132
- </a>
18133
- — <strong>${I18n.t("typedDimensionWarning.value")}</strong> ${fact.value}
18134
- </li>
18135
- `)}
18136
- </ul>
18137
- </li>
18138
- `)}
18139
- </ul>
18571
+ ${this._typedDimensionWarningFacts.length > 0 ? html`
18572
+ <div class="validate-warning-section">
18573
+ ${this._duplicateFactMismatches.length > 0 ? html`<h4>${I18n.t("typedDimensionWarning.title")}</h4>` : ""}
18574
+ <p>${I18n.t("typedDimensionWarning.message")}</p>
18575
+ <ul class="error-list">
18576
+ ${this._groupTypedDimensionWarningFactsByRole().map((group) => html`
18577
+ <li>
18578
+ <strong>${I18n.t("typedDimensionWarning.role")}</strong> ${group.sectionTitle}
18579
+ <ul>
18580
+ ${group.facts.map((fact) => html`
18581
+ <li>
18582
+ <strong>${I18n.t("typedDimensionWarning.field")}</strong>
18583
+ <a
18584
+ href="javascript:void(0)"
18585
+ class="error-field-link"
18586
+ @click="${() => this._handleTypedDimensionFactClick(fact.conceptId, fact.columnId, fact.sectionId)}"
18587
+ title="${I18n.t("typedDimensionWarning.clickToFocus")}"
18588
+ >
18589
+ ${fact.conceptLabel} (${fact.columnTitle})
18590
+ </a>
18591
+ — <strong>${I18n.t("typedDimensionWarning.value")}</strong> ${fact.value}
18592
+ </li>
18593
+ `)}
18594
+ </ul>
18595
+ </li>
18596
+ `)}
18597
+ </ul>
18598
+ </div>
18599
+ ` : ""}
18600
+ ${this._duplicateFactMismatches.length > 0 ? html`
18601
+ <div class="validate-warning-section">
18602
+ ${this._typedDimensionWarningFacts.length > 0 ? html`<h4>${I18n.t("duplicateFactWarning.title")}</h4>` : ""}
18603
+ <p>${I18n.t("duplicateFactWarning.message")}</p>
18604
+ <ul class="error-list">
18605
+ ${this._duplicateFactMismatches.map((mismatch) => html`
18606
+ <li>
18607
+ <strong>${mismatch.conceptLabel}</strong>
18608
+ <ul>
18609
+ ${mismatch.facts.map((fact) => html`
18610
+ <li>
18611
+ <strong>${I18n.t("duplicateFactWarning.role")}</strong> ${fact.sectionTitle} —
18612
+ <a
18613
+ href="javascript:void(0)"
18614
+ class="error-field-link"
18615
+ @click="${() => this._handleDuplicateFactClick(fact.conceptId, fact.columnId, fact.sectionId)}"
18616
+ title="${I18n.t("duplicateFactWarning.clickToFocus")}"
18617
+ >
18618
+ ${fact.columnTitle}
18619
+ </a>
18620
+ — <strong>${I18n.t("duplicateFactWarning.value")}</strong> ${fact.value}
18621
+ </li>
18622
+ `)}
18623
+ </ul>
18624
+ </li>
18625
+ `)}
18626
+ </ul>
18627
+ </div>
18628
+ ` : ""}
18140
18629
  </div>
18141
18630
  <div class="error-popup-footer">
18142
- <button class="btn-secondary" @click="${() => {
18143
- this._showTypedDimensionWarning = false;
18631
+ ${this._duplicateWarningPopupMode === "validate" ? html`
18632
+ <button class="btn-secondary" @click="${() => {
18633
+ this._showValidateWarningPopup = false;
18144
18634
  this.requestUpdate();
18145
18635
  }}">${I18n.t("typedDimensionWarning.cancel")}</button>
18146
- <button class="btn-primary" @click="${() => {
18147
- this._showTypedDimensionWarning = false;
18636
+ <button class="btn-primary" @click="${() => {
18637
+ this._showValidateWarningPopup = false;
18148
18638
  this._continueSubmit();
18149
18639
  }}">${I18n.t("typedDimensionWarning.continue")}</button>
18640
+ ` : html`
18641
+ <button class="btn-primary" @click="${() => {
18642
+ this._showValidateWarningPopup = false;
18643
+ this.requestUpdate();
18644
+ }}">${I18n.t("duplicateFactWarning.close")}</button>
18645
+ `}
18150
18646
  </div>
18151
18647
  </div>
18152
18648
  </div>
@@ -19023,6 +19519,17 @@ JupiterDynamicForm.styles = css`
19023
19519
  color: var(--jupiter-text-primary, #333);
19024
19520
  }
19025
19521
 
19522
+ .validate-warning-section + .validate-warning-section {
19523
+ margin-top: 24px;
19524
+ padding-top: 20px;
19525
+ border-top: 1px solid var(--jupiter-border-color, #ddd);
19526
+ }
19527
+
19528
+ .validate-warning-section h4 {
19529
+ margin: 0 0 8px 0;
19530
+ color: var(--jupiter-text-primary, #333);
19531
+ }
19532
+
19026
19533
  .error-list {
19027
19534
  list-style: none;
19028
19535
  padding: 0;
@@ -19262,10 +19769,19 @@ __decorateClass([
19262
19769
  ], JupiterDynamicForm.prototype, "_showErrorPopup", 2);
19263
19770
  __decorateClass([
19264
19771
  r()
19265
- ], JupiterDynamicForm.prototype, "_showTypedDimensionWarning", 2);
19772
+ ], JupiterDynamicForm.prototype, "_showValidateWarningPopup", 2);
19266
19773
  __decorateClass([
19267
19774
  r()
19268
19775
  ], JupiterDynamicForm.prototype, "_typedDimensionWarningFacts", 2);
19776
+ __decorateClass([
19777
+ r()
19778
+ ], JupiterDynamicForm.prototype, "_duplicateFactMismatches", 2);
19779
+ __decorateClass([
19780
+ r()
19781
+ ], JupiterDynamicForm.prototype, "_duplicateFactWarnings", 2);
19782
+ __decorateClass([
19783
+ r()
19784
+ ], JupiterDynamicForm.prototype, "_duplicateWarningPopupMode", 2);
19269
19785
  __decorateClass([
19270
19786
  r()
19271
19787
  ], JupiterDynamicForm.prototype, "_submitDisabled", 2);