jupiter-dynamic-forms 1.20.3 → 1.20.4

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
@@ -1668,7 +1668,8 @@ const form$1 = {
1668
1668
  download: "Download",
1669
1669
  cancelValidation: "Cancel Validation",
1670
1670
  lastValidationResults: "Last Validation Results",
1671
- validationInProgress: "Validation In Progress"
1671
+ validationInProgress: "Validation In Progress",
1672
+ quickValidate: "Quick Validate"
1672
1673
  };
1673
1674
  const filter$1 = {
1674
1675
  selectRoles: "Select Roles",
@@ -1873,7 +1874,8 @@ const form = {
1873
1874
  download: "Downloaden",
1874
1875
  cancelValidation: "Validatie annuleren",
1875
1876
  lastValidationResults: "Laatste validatieresultaten",
1876
- validationInProgress: "Validatie bezig"
1877
+ validationInProgress: "Validatie bezig",
1878
+ quickValidate: "Snel valideren"
1877
1879
  };
1878
1880
  const filter = {
1879
1881
  selectRoles: "Rollen selecteren",
@@ -2715,22 +2717,26 @@ function collectAllCandidates(formData) {
2715
2717
  }
2716
2718
  return candidates;
2717
2719
  }
2718
- function indexConceptTree(concept, conceptByQName, conceptById, childrenIndex) {
2720
+ function indexConceptTree(concept, conceptByQName, conceptById, conceptIdByQName, conceptIdByQNameHasChildren, childrenIndex) {
2719
2721
  const info = {
2720
2722
  conceptId: concept.id,
2721
- qname: concept.conceptName,
2723
+ qname: concept.name,
2722
2724
  balance: concept.balance
2723
2725
  };
2724
- if (!conceptById.has(concept.id))
2725
- conceptById.set(concept.id, info);
2726
- if (!conceptByQName.has(concept.conceptName))
2727
- conceptByQName.set(concept.conceptName, info);
2726
+ conceptById.set(concept.id, info);
2727
+ (conceptByQName.get(concept.name) ?? conceptByQName.set(concept.name, []).get(concept.name)).push(info);
2728
2728
  const children = concept.children || [];
2729
+ const hasOwnChildren = children.length > 0;
2730
+ const alreadyHasChildren = conceptIdByQNameHasChildren.get(concept.name) === true;
2731
+ if (!conceptIdByQName.has(concept.name) || hasOwnChildren && !alreadyHasChildren) {
2732
+ conceptIdByQName.set(concept.name, concept.id);
2733
+ conceptIdByQNameHasChildren.set(concept.name, hasOwnChildren);
2734
+ }
2729
2735
  childrenIndex.set(
2730
2736
  concept.id,
2731
2737
  children.map((child) => child.id)
2732
2738
  );
2733
- children.forEach((child) => indexConceptTree(child, conceptByQName, conceptById, childrenIndex));
2739
+ children.forEach((child) => indexConceptTree(child, conceptByQName, conceptById, conceptIdByQName, conceptIdByQNameHasChildren, childrenIndex));
2734
2740
  }
2735
2741
  function indexMemberTree(member, memberQNameToId) {
2736
2742
  if (!memberQNameToId.has(member.conceptName))
@@ -2740,21 +2746,23 @@ function indexMemberTree(member, memberQNameToId) {
2740
2746
  const PRESENTATION_TOTAL_GROUP_ACCESSORS = {
2741
2747
  getId: (concept) => concept.id,
2742
2748
  getPreferredLabel: (concept) => concept.preferredLabel,
2743
- isAbstract: (concept) => concept.elementAbstract,
2749
+ isAbstract: (concept) => concept.abstract === true,
2744
2750
  getChildren: (concept) => concept.children || []
2745
2751
  };
2746
- function buildFormulaResolutionContext(formData, columns, presentationRoles, hypercubeRoles, periodStartDate, periodEndDate) {
2752
+ function buildFormulaResolutionContext(formData, columns, sections, hypercubeRoles, periodStartDate, periodEndDate) {
2747
2753
  const conceptByQName = /* @__PURE__ */ new Map();
2748
2754
  const conceptById = /* @__PURE__ */ new Map();
2755
+ const conceptIdByQNamePerRole = /* @__PURE__ */ new Map();
2749
2756
  const childrenByRole = /* @__PURE__ */ new Map();
2750
2757
  const totalGroupChildrenByRole = /* @__PURE__ */ new Map();
2751
- presentationRoles.forEach((role) => {
2752
- var _a;
2753
- const concepts = ((_a = role.presentationLinkbase) == null ? void 0 : _a.concepts) || [];
2758
+ sections.forEach((section2) => {
2754
2759
  const childrenIndex = /* @__PURE__ */ new Map();
2755
- concepts.forEach((concept) => indexConceptTree(concept, conceptByQName, conceptById, childrenIndex));
2756
- childrenByRole.set(role.roleURI, childrenIndex);
2757
- totalGroupChildrenByRole.set(role.roleURI, buildTotalGroupChildrenIndex(concepts, PRESENTATION_TOTAL_GROUP_ACCESSORS));
2760
+ const conceptIdByQName = /* @__PURE__ */ new Map();
2761
+ const conceptIdByQNameHasChildren = /* @__PURE__ */ new Map();
2762
+ section2.concepts.forEach((concept) => indexConceptTree(concept, conceptByQName, conceptById, conceptIdByQName, conceptIdByQNameHasChildren, childrenIndex));
2763
+ childrenByRole.set(section2.roleURI, childrenIndex);
2764
+ conceptIdByQNamePerRole.set(section2.roleURI, conceptIdByQName);
2765
+ totalGroupChildrenByRole.set(section2.roleURI, buildTotalGroupChildrenIndex(section2.concepts, PRESENTATION_TOTAL_GROUP_ACCESSORS));
2758
2766
  });
2759
2767
  const dimensionQNameToId = /* @__PURE__ */ new Map();
2760
2768
  const memberQNameToId = /* @__PURE__ */ new Map();
@@ -2775,6 +2783,7 @@ function buildFormulaResolutionContext(formData, columns, presentationRoles, hyp
2775
2783
  periodEndDate,
2776
2784
  conceptByQName,
2777
2785
  conceptById,
2786
+ conceptIdByQNamePerRole,
2778
2787
  childrenByRole,
2779
2788
  totalGroupChildrenByRole,
2780
2789
  dimensionQNameToId,
@@ -2867,14 +2876,15 @@ function unionCandidates(lists) {
2867
2876
  return Array.from(merged.values());
2868
2877
  }
2869
2878
  function matchFilter(filter2, universe, ctx) {
2870
- var _a;
2879
+ var _a, _b;
2871
2880
  switch (filter2.type) {
2872
2881
  case "CONCEPT_NAME": {
2873
2882
  const qname = filter2.attributes["concept.qname"];
2874
- const info = qname ? ctx.conceptByQName.get(qname) : void 0;
2875
- if (!info)
2883
+ const infos = qname ? ctx.conceptByQName.get(qname) : void 0;
2884
+ if (!infos || infos.length === 0)
2876
2885
  return [];
2877
- return universe.filter((candidate) => candidate.conceptId === info.conceptId);
2886
+ const conceptIds = new Set(infos.map((info) => info.conceptId));
2887
+ return universe.filter((candidate) => conceptIds.has(candidate.conceptId));
2878
2888
  }
2879
2889
  case "CONCEPT_BALANCE": {
2880
2890
  const balance = filter2.attributes["balance"];
@@ -2887,16 +2897,16 @@ function matchFilter(filter2, universe, ctx) {
2887
2897
  const anchorQName = filter2.attributes["qname"];
2888
2898
  const linkrole = filter2.attributes["linkrole"];
2889
2899
  const axis = filter2.attributes["axis"];
2890
- const anchorInfo = anchorQName ? ctx.conceptByQName.get(anchorQName) : void 0;
2900
+ const anchorConceptId = anchorQName && linkrole ? (_a = ctx.conceptIdByQNamePerRole.get(linkrole)) == null ? void 0 : _a.get(anchorQName) : void 0;
2891
2901
  const childrenIndex = linkrole ? ctx.childrenByRole.get(linkrole) : void 0;
2892
- if (!anchorInfo || !childrenIndex)
2902
+ if (!anchorConceptId || !childrenIndex)
2893
2903
  return [];
2894
2904
  let relatedConceptIds;
2895
2905
  if (axis === "descendant") {
2896
- relatedConceptIds = collectDescendantConceptIds(childrenIndex, anchorInfo.conceptId);
2906
+ relatedConceptIds = collectDescendantConceptIds(childrenIndex, anchorConceptId);
2897
2907
  } else {
2898
- const nestedChildren = childrenIndex.get(anchorInfo.conceptId) || [];
2899
- relatedConceptIds = nestedChildren.length > 0 ? nestedChildren : ((_a = ctx.totalGroupChildrenByRole.get(linkrole ?? "")) == null ? void 0 : _a.get(anchorInfo.conceptId)) || [];
2908
+ const nestedChildren = childrenIndex.get(anchorConceptId) || [];
2909
+ relatedConceptIds = nestedChildren.length > 0 ? nestedChildren : ((_b = ctx.totalGroupChildrenByRole.get(linkrole ?? "")) == null ? void 0 : _b.get(anchorConceptId)) || [];
2900
2910
  }
2901
2911
  const relatedSet = new Set(relatedConceptIds);
2902
2912
  return universe.filter((candidate) => relatedSet.has(candidate.conceptId));
@@ -2924,6 +2934,8 @@ function matchFilter(filter2, universe, ctx) {
2924
2934
  }
2925
2935
  case "OR_FILTER":
2926
2936
  return unionCandidates(filter2.children.map((child) => resolveFilter(child, universe, ctx)));
2937
+ case "ASPECT_COVER":
2938
+ return universe;
2927
2939
  default:
2928
2940
  return [];
2929
2941
  }
@@ -2976,6 +2988,27 @@ function resolveFormulaVariableUsedFallback(variable, ctx) {
2976
2988
  return true;
2977
2989
  return void 0;
2978
2990
  }
2991
+ function collectConceptNameQNames(filters) {
2992
+ const qnames = [];
2993
+ for (const filter2 of filters) {
2994
+ if (filter2.type === "CONCEPT_NAME") {
2995
+ const qname = filter2.attributes["concept.qname"];
2996
+ if (qname)
2997
+ qnames.push(qname);
2998
+ } else if (filter2.type === "OR_FILTER") {
2999
+ qnames.push(...collectConceptNameQNames(filter2.children));
3000
+ }
3001
+ }
3002
+ return qnames;
3003
+ }
3004
+ function resolveVariableConceptQNames(variable) {
3005
+ return collectConceptNameQNames(variable.filters);
3006
+ }
3007
+ function resolveVariableColumnsForConcept(variable, targetConceptId, ctx) {
3008
+ const universe = ctx.columns.map((column2) => ({ conceptId: targetConceptId, columnId: column2.id }));
3009
+ const resolved = intersectCandidates(variable.filters.map((filter2) => resolveFilter(filter2, universe, ctx)));
3010
+ return resolved.map((candidate) => candidate.columnId);
3011
+ }
2979
3012
  function buildAssertionBindings(assertion, ctx) {
2980
3013
  const bindings = /* @__PURE__ */ new Map();
2981
3014
  for (const variable of assertion.variables) {
@@ -3074,10 +3107,10 @@ class FormulaValidationService {
3074
3107
  if (assertion.type === "EXISTENCE_ASSERTION") {
3075
3108
  const matchedFactCount = resolveFormulaVariableMatchCount(assertion.variables[0], ctx);
3076
3109
  const satisfied2 = evaluateExistenceTest(assertion.test, matchedFactCount);
3077
- return this._evaluatedResult(assertion, role, satisfied2, language, matchedFactCount);
3110
+ return this._evaluatedResult(assertion, role, ctx, satisfied2, language, matchedFactCount);
3078
3111
  }
3079
3112
  const satisfied = evaluateFormulaTest(assertion.test, bindings, fallbackUsage);
3080
- return this._evaluatedResult(assertion, role, satisfied, language);
3113
+ return this._evaluatedResult(assertion, role, ctx, satisfied, language);
3081
3114
  } catch (error2) {
3082
3115
  if (error2 instanceof FormulaExpressionError) {
3083
3116
  console.warn(`[FormulaValidationService] Assertion "${assertion.id}" could not be evaluated (${error2.reason}): ${error2.message}`);
@@ -3098,7 +3131,8 @@ class FormulaValidationService {
3098
3131
  skipReason
3099
3132
  };
3100
3133
  }
3101
- _evaluatedResult(assertion, role, satisfied, language, matchedFactCount) {
3134
+ _evaluatedResult(assertion, role, ctx, satisfied, language, matchedFactCount) {
3135
+ const message = satisfied ? void 0 : this._pickMessage(assertion, language);
3102
3136
  return {
3103
3137
  assertionId: assertion.id,
3104
3138
  roleURI: role.roleURI,
@@ -3107,8 +3141,9 @@ class FormulaValidationService {
3107
3141
  severity: assertion.severity,
3108
3142
  satisfied,
3109
3143
  skipped: false,
3110
- message: satisfied ? void 0 : this._pickMessage(assertion, language),
3111
- matchedFactCount
3144
+ message,
3145
+ matchedFactCount,
3146
+ conceptLinks: message ? this._buildConceptLinks(assertion, role.roleURI, ctx, message) : void 0
3112
3147
  };
3113
3148
  }
3114
3149
  _pickMessage(assertion, language) {
@@ -3116,7 +3151,55 @@ class FormulaValidationService {
3116
3151
  if (messages.length === 0)
3117
3152
  return void 0;
3118
3153
  const message = messages.find((m) => m.xmlLang === language) ?? messages.find((m) => m.xmlLang === "en") ?? messages[0];
3119
- return message.text;
3154
+ return this._stripTechnicalDetail(message.text);
3155
+ }
3156
+ /**
3157
+ * Taxonomy-authored messages append raw XBRL Formula/XPath debug expressions after the
3158
+ * human-readable sentence, delimited by " | {...}" (e.g. `xff:has-fallback-value(...)`,
3159
+ * `varArc_...` variable names) — see the fixture's `*_formula.json` message text. That debug
3160
+ * payload is meaningless to a non-technical filer, so only the sentence preceding it is shown.
3161
+ */
3162
+ _stripTechnicalDetail(text) {
3163
+ const braceIndex = text.indexOf("{");
3164
+ if (braceIndex === -1)
3165
+ return text.trim();
3166
+ return text.slice(0, braceIndex).replace(/\|\s*$/, "").trim();
3167
+ }
3168
+ /**
3169
+ * JDF-059: resolves every concept the assertion's variables name directly (JDF-045's
3170
+ * `resolveVariableConceptQNames`) to a clickable fact location — but only the ones the
3171
+ * (already-stripped) message text actually mentions by backtick-quoted local name, e.g.
3172
+ * `` `Equity` `` — a concept a variable references internally (say, as a dimensional
3173
+ * exclusion) but the message never calls out by name has nothing for the user to click on, so
3174
+ * it's left out rather than guessed at. Column resolution reuses the assertion's own filters
3175
+ * (`resolveVariableColumnsForConcept`), so the link always points at the exact fact this
3176
+ * specific assertion run cared about — never just "some field for this concept" — including an
3177
+ * empty one for a failed "MUST exist" assertion, so the user can navigate straight to where the
3178
+ * missing value belongs.
3179
+ */
3180
+ _buildConceptLinks(assertion, roleURI, ctx, message) {
3181
+ var _a, _b, _c;
3182
+ const links = [];
3183
+ const seenLabels = /* @__PURE__ */ new Set();
3184
+ for (const variable of assertion.variables) {
3185
+ for (const qname of resolveVariableConceptQNames(variable)) {
3186
+ const conceptId = ((_a = ctx.conceptIdByQNamePerRole.get(roleURI)) == null ? void 0 : _a.get(qname)) ?? ((_c = (_b = ctx.conceptByQName.get(qname)) == null ? void 0 : _b[0]) == null ? void 0 : _c.conceptId);
3187
+ if (!conceptId)
3188
+ continue;
3189
+ const label = qname.includes(":") ? qname.split(":").pop() : qname;
3190
+ if (seenLabels.has(label))
3191
+ continue;
3192
+ if (!new RegExp("`" + this._escapeRegExp(label) + "`").test(message))
3193
+ continue;
3194
+ const columns = resolveVariableColumnsForConcept(variable, conceptId, ctx);
3195
+ links.push({ label, conceptQName: qname, conceptId, columnId: columns[0] });
3196
+ seenLabels.add(label);
3197
+ }
3198
+ }
3199
+ return links;
3200
+ }
3201
+ _escapeRegExp(text) {
3202
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3120
3203
  }
3121
3204
  }
3122
3205
  class FactMatcher {
@@ -10290,6 +10373,35 @@ let JupiterFormulaValidationDialog = class extends LitElement {
10290
10373
  }
10291
10374
  this.requestUpdate();
10292
10375
  }
10376
+ // JDF-059: navigates the host form to the exact fact this assertion's variable resolved to
10377
+ // (or its empty target cell, for a "must exist" failure) and closes this dialog, mirroring the
10378
+ // existing "click a validation error, land on its field" flow this feature deliberately reuses
10379
+ // rather than building a second focus mechanism.
10380
+ _handleConceptLinkClick(link) {
10381
+ this.dispatchEvent(new CustomEvent("formula-concept-click", {
10382
+ detail: { conceptId: link.conceptId, conceptQName: link.conceptQName, columnId: link.columnId },
10383
+ bubbles: true,
10384
+ composed: true
10385
+ }));
10386
+ }
10387
+ // Splits a message on its backtick-quoted concept mentions and renders the ones this
10388
+ // assertion resolved a fact location for (`result.conceptLinks`) as click-to-focus buttons;
10389
+ // any other backtick mention (e.g. an enumerated value like `Na`, not a concept) renders as
10390
+ // plain text with the backticks dropped, since there is nothing to link it to.
10391
+ _renderMessage(result) {
10392
+ const message = result.message ?? "";
10393
+ const linkByLabel = new Map((result.conceptLinks ?? []).map((link) => [link.label, link]));
10394
+ const parts = message.split(/(`[^`]+`)/g);
10395
+ return html`${parts.map((part) => {
10396
+ const match = /^`([^`]+)`$/.exec(part);
10397
+ if (!match)
10398
+ return part;
10399
+ const link = linkByLabel.get(match[1]);
10400
+ if (!link)
10401
+ return match[1];
10402
+ return html`<button type="button" class="concept-link" @click="${() => this._handleConceptLinkClick(link)}">${match[1]}</button>`;
10403
+ })}`;
10404
+ }
10293
10405
  _groupByRole(failures) {
10294
10406
  const grouped = failures.reduce((groups, result) => {
10295
10407
  var _a;
@@ -10338,7 +10450,7 @@ let JupiterFormulaValidationDialog = class extends LitElement {
10338
10450
  <span class="severity-badge ${result.severity === "ERROR" ? "error" : "warning"}">
10339
10451
  ${result.severity === "ERROR" ? I18n.t("formulaValidation.severityError") : I18n.t("formulaValidation.severityWarning")}
10340
10452
  </span>
10341
- <span class="assertion-message">${result.message}</span>
10453
+ <span class="assertion-message">${this._renderMessage(result)}</span>
10342
10454
  </div>
10343
10455
  `)}
10344
10456
  </div>
@@ -10533,6 +10645,23 @@ JupiterFormulaValidationDialog.styles = css`
10533
10645
  line-height: 1.4;
10534
10646
  }
10535
10647
 
10648
+ .concept-link {
10649
+ font: inherit;
10650
+ font-weight: 600;
10651
+ color: var(--buttonBgColor, var(--jupiter-primary-color, #1976d2));
10652
+ background: none;
10653
+ border: none;
10654
+ padding: 0;
10655
+ margin: 0;
10656
+ cursor: pointer;
10657
+ text-decoration: underline;
10658
+ text-underline-offset: 2px;
10659
+ }
10660
+
10661
+ .concept-link:hover {
10662
+ opacity: 0.8;
10663
+ }
10664
+
10536
10665
  .dialog-actions {
10537
10666
  display: flex;
10538
10667
  gap: 12px;
@@ -11381,6 +11510,14 @@ let JupiterDynamicForm = class extends LitElement {
11381
11510
  _handleFormulaValidationDialogCancel() {
11382
11511
  this._showFormulaValidationDialog = false;
11383
11512
  }
11513
+ // JDF-059: a concept name inside a formula-validation message was clicked — close the popup
11514
+ // (mirrors the existing "click a validation error, land on its field" UX from the host-rendered
11515
+ // show-validation-results flow) and jump straight to the exact fact the assertion's own variable
11516
+ // resolution identified, via `columnId` rather than re-deriving it from dimensions.
11517
+ _handleFormulaConceptClick(event) {
11518
+ this._showFormulaValidationDialog = false;
11519
+ this.scrollToConcept(event.detail.conceptQName, void 0, void 0, event.detail.columnId);
11520
+ }
11384
11521
  _handleRoleFilterApply(event) {
11385
11522
  var _a, _b;
11386
11523
  const { selectedRoleIds, periodPreferences } = event.detail;
@@ -12519,7 +12656,6 @@ let JupiterDynamicForm = class extends LitElement {
12519
12656
  return;
12520
12657
  }
12521
12658
  this._correctLegacyPeriodStartInstantDates();
12522
- this._runFormulaValidationPreflight();
12523
12659
  const submissionData = this._generateSubmissionData();
12524
12660
  console.log("📊 Form Submission Data:", JSON.stringify(submissionData, null, 2));
12525
12661
  console.log("📊 Submission Data Summary:");
@@ -12541,6 +12677,16 @@ let JupiterDynamicForm = class extends LitElement {
12541
12677
  this._submitDisabled = false;
12542
12678
  }, 1e3);
12543
12679
  }
12680
+ /**
12681
+ * Handler for the standalone "Quick Validate" button (visible only when `formulaEnabled` is
12682
+ * true). Runs the formula-assertion pre-flight in isolation from the original Validate/submit
12683
+ * button, which no longer triggers formula validation as a side effect.
12684
+ */
12685
+ _handleQuickValidate() {
12686
+ if (this.mode === "admin")
12687
+ return;
12688
+ this._runFormulaValidationPreflight();
12689
+ }
12544
12690
  /**
12545
12691
  * JDF-049: runs FormulaValidationService against the live form state and stores the results
12546
12692
  * for the results dialog (JDF-050). Graceful no-op — per JDF-048's gating and this ticket's
@@ -12550,21 +12696,42 @@ let JupiterDynamicForm = class extends LitElement {
12550
12696
  * cost when off" requirement).
12551
12697
  */
12552
12698
  _runFormulaValidationPreflight() {
12553
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
12699
+ var _a, _b, _c, _d, _e, _f, _g, _h;
12554
12700
  if (!this.formulaEnabled)
12555
12701
  return;
12556
12702
  const formulaRoles = (_c = (_b = (_a = this.xbrlInput) == null ? void 0 : _a.formula) == null ? void 0 : _b[0]) == null ? void 0 : _c.roles;
12557
12703
  if (!formulaRoles || formulaRoles.length === 0)
12558
12704
  return;
12705
+ const scopeSections = ((_e = (_d = this._currentSchema) == null ? void 0 : _d.sections) == null ? void 0 : _e.length) ? this._currentSchema.sections : this._allSections;
12706
+ if (!scopeSections || scopeSections.length === 0)
12707
+ return;
12708
+ const visibleRoleURIs = new Set(scopeSections.map((section2) => {
12709
+ var _a2;
12710
+ return ((_a2 = section2.metadata) == null ? void 0 : _a2.roleURI) || section2.id;
12711
+ }));
12712
+ const scopedFormulaRoles = formulaRoles.filter((role) => visibleRoleURIs.has(role.roleURI));
12713
+ if (scopedFormulaRoles.length === 0)
12714
+ return;
12715
+ const conceptSections = scopeSections.map((section2) => {
12716
+ var _a2;
12717
+ return {
12718
+ roleURI: ((_a2 = section2.metadata) == null ? void 0 : _a2.roleURI) || section2.id,
12719
+ concepts: section2.concepts
12720
+ };
12721
+ });
12559
12722
  const ctx = buildFormulaResolutionContext(
12560
12723
  this._formData,
12561
12724
  this._mergeAllSectionColumns(),
12562
- ((_f = (_e = (_d = this.xbrlInput) == null ? void 0 : _d.presentation) == null ? void 0 : _e[0]) == null ? void 0 : _f.roles) || [],
12563
- (_i = (_h = (_g = this.xbrlInput) == null ? void 0 : _g.hypercubes) == null ? void 0 : _h[0]) == null ? void 0 : _i.roles,
12725
+ conceptSections,
12726
+ (_h = (_g = (_f = this.xbrlInput) == null ? void 0 : _f.hypercubes) == null ? void 0 : _g[0]) == null ? void 0 : _h.roles,
12564
12727
  this.periodStartDate,
12565
12728
  this.periodEndDate
12566
12729
  );
12567
- const results = this._formulaValidationService.evaluate(this.xbrlInput, ctx, this.language);
12730
+ const scopedXbrlInput = {
12731
+ ...this.xbrlInput,
12732
+ formula: [{ ...this.xbrlInput.formula[0], roles: scopedFormulaRoles }]
12733
+ };
12734
+ const results = this._formulaValidationService.evaluate(scopedXbrlInput, ctx, this.language);
12568
12735
  const summary = this._formulaValidationService.summarize(results);
12569
12736
  this._formulaValidationResults = results;
12570
12737
  this._formulaValidationSummary = summary;
@@ -14728,9 +14895,9 @@ let JupiterDynamicForm = class extends LitElement {
14728
14895
  return dims.length === 1 && this._normalizeAxisId(col.dimensionData.axisId ?? "") === this._normalizeAxisId(dims[0].axis) && this._normalizeMemberId(col.dimensionData.memberId ?? "") === this._normalizeMemberId(dims[0].member);
14729
14896
  })) == null ? void 0 : _a.id;
14730
14897
  }
14731
- async scrollToConcept(conceptName, dimensions, match) {
14898
+ async scrollToConcept(conceptName, dimensions, match, columnId) {
14732
14899
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
14733
- console.log(`[scrollToConcept] ▶ START conceptName=${conceptName} value=${match == null ? void 0 : match.value} dims=${JSON.stringify(dimensions)}`);
14900
+ console.log(`[scrollToConcept] ▶ START conceptName=${conceptName} value=${match == null ? void 0 : match.value} dims=${JSON.stringify(dimensions)} columnId=${columnId}`);
14734
14901
  let targetSection = null;
14735
14902
  let targetConcept = null;
14736
14903
  const sectionsToSearch = [
@@ -14744,7 +14911,19 @@ let JupiterDynamicForm = class extends LitElement {
14744
14911
  for (const section2 of sectionsToSearch) {
14745
14912
  const found = this._findConceptByName(section2.concepts, conceptName);
14746
14913
  if (found) {
14747
- if (dimensions == null ? void 0 : dimensions.length) {
14914
+ if (columnId) {
14915
+ const cols = section2.columns ?? this._columns;
14916
+ const hasColumn = cols.some((c2) => c2.id === columnId);
14917
+ console.log(`[scrollToConcept] Section "${section2.id}" has concept "${found.id}" | columnId="${columnId}" present=${hasColumn}`);
14918
+ if (hasColumn) {
14919
+ targetSection = section2;
14920
+ targetConcept = found;
14921
+ break;
14922
+ } else if (!targetSection) {
14923
+ targetSection = section2;
14924
+ targetConcept = found;
14925
+ }
14926
+ } else if (dimensions == null ? void 0 : dimensions.length) {
14748
14927
  const cols = section2.columns ?? this._columns;
14749
14928
  const colId = this._findColumnByDimensions(cols, dimensions);
14750
14929
  console.log(`[scrollToConcept] Section "${section2.id}" has concept "${found.id}" | colMatch=${colId ?? "null"} | cols=${cols.map((c2) => c2.id).join(",")}`);
@@ -14777,7 +14956,9 @@ let JupiterDynamicForm = class extends LitElement {
14777
14956
  let targetColumnId = null;
14778
14957
  const hasValueMatch = (match == null ? void 0 : match.value) !== void 0 && (match == null ? void 0 : match.value) !== null;
14779
14958
  const targetValue = hasValueMatch ? String(match.value) : null;
14780
- if (dimensions == null ? void 0 : dimensions.length) {
14959
+ if (columnId) {
14960
+ targetColumnId = columnId;
14961
+ } else if (dimensions == null ? void 0 : dimensions.length) {
14781
14962
  targetColumnId = this._findColumnByDimensions(columns, dimensions) ?? null;
14782
14963
  } else if (!hasValueMatch) {
14783
14964
  targetColumnId = ((_b = columns[0]) == null ? void 0 : _b.id) ?? null;
@@ -15023,6 +15204,15 @@ let JupiterDynamicForm = class extends LitElement {
15023
15204
  </button>
15024
15205
  ` : ""}
15025
15206
 
15207
+ ${this.formulaEnabled && this.mode !== "admin" ? html`
15208
+ <button
15209
+ class="btn-secondary btn-quick-validate"
15210
+ @click="${this._handleQuickValidate}"
15211
+ ?disabled="${this.disabled || this.readonly || this._validationStatus === "inProgress"}"
15212
+ >
15213
+ ${I18n.t("form.quickValidate")}
15214
+ </button>
15215
+ ` : ""}
15026
15216
 
15027
15217
  <button
15028
15218
  class="btn-primary"
@@ -15060,6 +15250,7 @@ let JupiterDynamicForm = class extends LitElement {
15060
15250
  .results="${this._formulaValidationResults}"
15061
15251
  .summary="${this._formulaValidationSummary}"
15062
15252
  @dialog-cancel="${this._handleFormulaValidationDialogCancel}"
15253
+ @formula-concept-click="${this._handleFormulaConceptClick}"
15063
15254
  @click="${this._handleFormulaValidationDialogCancel}"
15064
15255
  ></jupiter-formula-validation-dialog>
15065
15256
  ` : ""}