jupiter-dynamic-forms 1.22.5 → 1.22.6

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
@@ -2894,6 +2894,7 @@ function collectAllCandidates(formData) {
2894
2894
  return candidates;
2895
2895
  }
2896
2896
  function indexConceptTree(concept, conceptByQName, conceptById, conceptIdByQName, conceptIdByQNameHasChildren, childrenIndex) {
2897
+ var _a;
2897
2898
  const info = {
2898
2899
  conceptId: concept.id,
2899
2900
  qname: concept.name,
@@ -2904,8 +2905,11 @@ function indexConceptTree(concept, conceptByQName, conceptById, conceptIdByQName
2904
2905
  (conceptByQName.get(concept.name) ?? conceptByQName.set(concept.name, []).get(concept.name)).push(info);
2905
2906
  const children = concept.children || [];
2906
2907
  const hasOwnChildren = children.length > 0;
2908
+ const existingConceptId = conceptIdByQName.get(concept.name);
2907
2909
  const alreadyHasChildren = conceptIdByQNameHasChildren.get(concept.name) === true;
2908
- if (!conceptIdByQName.has(concept.name) || hasOwnChildren && !alreadyHasChildren) {
2910
+ const existingEffectivePeriodRole = existingConceptId ? (_a = conceptById.get(existingConceptId)) == null ? void 0 : _a.effectivePeriodRole : void 0;
2911
+ const shouldPreferPeriodEnd = hasOwnChildren && alreadyHasChildren && concept.effectivePeriodRole === "periodEndLabel" && existingEffectivePeriodRole === "periodStartLabel";
2912
+ if (!conceptIdByQName.has(concept.name) || hasOwnChildren && !alreadyHasChildren || shouldPreferPeriodEnd) {
2909
2913
  conceptIdByQName.set(concept.name, concept.id);
2910
2914
  conceptIdByQNameHasChildren.set(concept.name, hasOwnChildren);
2911
2915
  }
@@ -3360,6 +3364,42 @@ function uncoveredExplicitDimensionAxisIds(variable, ctx) {
3360
3364
  }
3361
3365
  return axisIds;
3362
3366
  }
3367
+ function coveredExplicitDimensionAxisIds(variable, ctx) {
3368
+ const axisIds = /* @__PURE__ */ new Set();
3369
+ for (const filter2 of variable.filters) {
3370
+ if (filter2.type !== "EXPLICIT_DIMENSION" || !filter2.cover)
3371
+ continue;
3372
+ const axisId = ctx.dimensionQNameToId.get(filter2.attributes["dimension.qname"] ?? "");
3373
+ if (axisId)
3374
+ axisIds.add(axisId);
3375
+ }
3376
+ return axisIds;
3377
+ }
3378
+ function axisIdsOnCandidates(candidates, ctx) {
3379
+ const axisIds = /* @__PURE__ */ new Set();
3380
+ candidates.forEach((candidate) => {
3381
+ var _a;
3382
+ const dimensionData = (_a = getColumn(ctx, candidate)) == null ? void 0 : _a.dimensionData;
3383
+ if (!dimensionData)
3384
+ return;
3385
+ if (dimensionData.axisId)
3386
+ axisIds.add(dimensionData.axisId);
3387
+ (dimensionData.combinations || []).forEach((combo) => {
3388
+ if (combo.axisId)
3389
+ axisIds.add(combo.axisId);
3390
+ });
3391
+ });
3392
+ return axisIds;
3393
+ }
3394
+ function impliedUncoveredAxisIds(variable, candidates, ctx) {
3395
+ const axisIds = uncoveredExplicitDimensionAxisIds(variable, ctx);
3396
+ const covered = coveredExplicitDimensionAxisIds(variable, ctx);
3397
+ axisIdsOnCandidates(candidates, ctx).forEach((axisId) => {
3398
+ if (!covered.has(axisId))
3399
+ axisIds.add(axisId);
3400
+ });
3401
+ return axisIds;
3402
+ }
3363
3403
  function candidateMemberIdForAxis(ctx, candidate, axisId) {
3364
3404
  var _a;
3365
3405
  const column2 = getColumn(ctx, candidate);
@@ -3369,21 +3409,39 @@ function candidateMemberIdForAxis(ctx, candidate, axisId) {
3369
3409
  return column2.dimensionData.memberId;
3370
3410
  return (_a = (column2.dimensionData.combinations || []).find((combo) => combo.axisId === axisId)) == null ? void 0 : _a.memberId;
3371
3411
  }
3412
+ function coveredMemberIdForAxis(variable, axisId, ctx) {
3413
+ for (const filter2 of variable.filters) {
3414
+ if (filter2.type !== "EXPLICIT_DIMENSION" || !filter2.cover || filter2.complement)
3415
+ continue;
3416
+ if (filter2.attributes["member.axis"])
3417
+ continue;
3418
+ if (ctx.dimensionQNameToId.get(filter2.attributes["dimension.qname"] ?? "") !== axisId)
3419
+ continue;
3420
+ const memberId = ctx.memberQNameToId.get(filter2.attributes["member.qname"] ?? "");
3421
+ if (memberId)
3422
+ return memberId;
3423
+ }
3424
+ return void 0;
3425
+ }
3372
3426
  function findUncoveredAxisAnchor(variables, axisId, candidatesByName, ctx) {
3373
3427
  for (const variable of variables) {
3374
3428
  if (variable.bindAsSequence)
3375
3429
  continue;
3376
- if (!uncoveredExplicitDimensionAxisIds(variable, ctx).has(axisId))
3377
- continue;
3378
3430
  const candidates = candidatesByName.get(variable.name) || [];
3379
- const memberIds = new Set(
3380
- candidates.map((candidate) => candidateMemberIdForAxis(ctx, candidate, axisId)).filter((id) => id !== void 0)
3381
- );
3382
- if (memberIds.size === 1)
3383
- return { memberId: [...memberIds][0], variableName: variable.name, ambiguous: false };
3384
- const rootMembers = [...memberIds].filter((id) => !ctx.memberIdsWithParent.has(id));
3385
- if (rootMembers.length === 1)
3386
- return { memberId: rootMembers[0], variableName: variable.name, ambiguous: true };
3431
+ if (impliedUncoveredAxisIds(variable, candidates, ctx).has(axisId)) {
3432
+ const memberIds = new Set(
3433
+ candidates.map((candidate) => candidateMemberIdForAxis(ctx, candidate, axisId)).filter((id) => id !== void 0)
3434
+ );
3435
+ if (memberIds.size === 1)
3436
+ return { memberId: [...memberIds][0], variableName: variable.name, ambiguous: false };
3437
+ const rootMembers = [...memberIds].filter((id) => !ctx.memberIdsWithParent.has(id));
3438
+ if (rootMembers.length === 1)
3439
+ return { memberId: rootMembers[0], variableName: variable.name, ambiguous: true };
3440
+ continue;
3441
+ }
3442
+ const pinnedMemberId = coveredMemberIdForAxis(variable, axisId, ctx);
3443
+ if (pinnedMemberId)
3444
+ return { memberId: pinnedMemberId, variableName: variable.name, ambiguous: false };
3387
3445
  }
3388
3446
  return void 0;
3389
3447
  }
@@ -3392,13 +3450,15 @@ function computeAssertionNarrowedCandidatesByName(variables, ctx, implicitFilter
3392
3450
  variables.forEach((variable) => candidatesByName.set(variable.name, resolveNarrowedCandidates(variable, ctx, roleURI)));
3393
3451
  if (implicitFiltering) {
3394
3452
  const axisIds = /* @__PURE__ */ new Set();
3395
- variables.forEach((variable) => uncoveredExplicitDimensionAxisIds(variable, ctx).forEach((axisId) => axisIds.add(axisId)));
3453
+ variables.forEach(
3454
+ (variable) => impliedUncoveredAxisIds(variable, candidatesByName.get(variable.name) || [], ctx).forEach((axisId) => axisIds.add(axisId))
3455
+ );
3396
3456
  axisIds.forEach((axisId) => {
3397
3457
  const anchor = findUncoveredAxisAnchor(variables, axisId, candidatesByName, ctx);
3398
3458
  if (!anchor)
3399
3459
  return;
3400
3460
  variables.forEach((variable) => {
3401
- const isNarrowableSequence = variable.bindAsSequence && uncoveredExplicitDimensionAxisIds(variable, ctx).has(axisId);
3461
+ const isNarrowableSequence = variable.bindAsSequence && impliedUncoveredAxisIds(variable, candidatesByName.get(variable.name) || [], ctx).has(axisId);
3402
3462
  const isAmbiguousAnchorItself = anchor.ambiguous && variable.name === anchor.variableName;
3403
3463
  if (!isNarrowableSequence && !isAmbiguousAnchorItself)
3404
3464
  return;
@@ -5011,6 +5071,15 @@ let JupiterFormField = class extends LitElement {
5011
5071
  this._availableUnits = [];
5012
5072
  this._numericDraftValue = null;
5013
5073
  this._focused = false;
5074
+ this._boundHandlePeriodPopupKeydown = this._handlePeriodPopupKeydown.bind(this);
5075
+ }
5076
+ connectedCallback() {
5077
+ super.connectedCallback();
5078
+ window.addEventListener("keydown", this._boundHandlePeriodPopupKeydown);
5079
+ }
5080
+ disconnectedCallback() {
5081
+ window.removeEventListener("keydown", this._boundHandlePeriodPopupKeydown);
5082
+ super.disconnectedCallback();
5014
5083
  }
5015
5084
  willUpdate(changedProperties) {
5016
5085
  var _a;
@@ -5936,18 +6005,14 @@ let JupiterFormField = class extends LitElement {
5936
6005
  }
5937
6006
  return html``;
5938
6007
  }
5939
- // JDF-118: the period popup is a plain Lit overlay, not a native <dialog>, so it doesn't get
5940
- // Escape-to-close for free the way the info/text dialogs do. Keydown on the overlay catches it
5941
- // as it bubbles up from whichever control inside the popup has focus.
5942
6008
  _handlePeriodPopupKeydown(e2) {
5943
- if (e2.key === "Escape") {
5944
- e2.stopPropagation();
6009
+ if (this._showPeriodPopup && e2.key === "Escape") {
5945
6010
  this._closePeriodPopup();
5946
6011
  }
5947
6012
  }
5948
6013
  _renderPeriodPopup() {
5949
6014
  return html`
5950
- <div class="period-popup-overlay" @click="${this._handlePopupOverlayClick}" @keydown="${this._handlePeriodPopupKeydown}">
6015
+ <div class="period-popup-overlay" @click="${this._handlePopupOverlayClick}">
5951
6016
  <div class="period-popup" @click="${(e2) => e2.stopPropagation()}">
5952
6017
  <div class="period-popup-header">
5953
6018
  <div class="period-popup-title">${I18n.t("field.editPeriod")}</div>
@@ -7671,6 +7736,7 @@ let JupiterAddColumnDialog = class extends LitElement {
7671
7736
  this._selectedType = "duration";
7672
7737
  this._selectedDimensions = /* @__PURE__ */ new Map();
7673
7738
  this._typedValueErrors = {};
7739
+ this._boundHandleKeydown = this._handleKeydown.bind(this);
7674
7740
  }
7675
7741
  updated(changedProperties) {
7676
7742
  if (changedProperties.has("open") && this.open) {
@@ -7682,6 +7748,11 @@ let JupiterAddColumnDialog = class extends LitElement {
7682
7748
  connectedCallback() {
7683
7749
  super.connectedCallback();
7684
7750
  this._resetForm();
7751
+ window.addEventListener("keydown", this._boundHandleKeydown);
7752
+ }
7753
+ disconnectedCallback() {
7754
+ window.removeEventListener("keydown", this._boundHandleKeydown);
7755
+ super.disconnectedCallback();
7685
7756
  }
7686
7757
  willUpdate(changedProperties) {
7687
7758
  super.willUpdate(changedProperties);
@@ -7689,6 +7760,11 @@ let JupiterAddColumnDialog = class extends LitElement {
7689
7760
  this._resetForm();
7690
7761
  }
7691
7762
  }
7763
+ _handleKeydown(event) {
7764
+ if (this.open && event.key === "Escape") {
7765
+ this._handleCancel();
7766
+ }
7767
+ }
7692
7768
  _handleCancel() {
7693
7769
  this.open = false;
7694
7770
  this.dispatchEvent(new CustomEvent("dialog-cancel", { bubbles: true }));
@@ -10265,6 +10341,7 @@ let JupiterAdvancedFilter = class extends LitElement {
10265
10341
  return;
10266
10342
  }
10267
10343
  if (e2.key === "Escape") {
10344
+ e2.stopPropagation();
10268
10345
  this._showSuggestions = false;
10269
10346
  this._activeSuggestionIndex = -1;
10270
10347
  return;
@@ -10579,11 +10656,22 @@ let JupiterFilterRolesDialog = class extends LitElement {
10579
10656
  this._showFactsOnly = false;
10580
10657
  this._conceptSearchText = "";
10581
10658
  this._collapsedRoles = /* @__PURE__ */ new Set();
10659
+ this._boundHandleKeydown = this._handleKeydown.bind(this);
10582
10660
  }
10583
10661
  connectedCallback() {
10584
10662
  super.connectedCallback();
10585
10663
  this._initializeTempSelection();
10586
10664
  this._initializePeriodPreferences();
10665
+ window.addEventListener("keydown", this._boundHandleKeydown);
10666
+ }
10667
+ disconnectedCallback() {
10668
+ window.removeEventListener("keydown", this._boundHandleKeydown);
10669
+ super.disconnectedCallback();
10670
+ }
10671
+ _handleKeydown(event) {
10672
+ if (this.open && event.key === "Escape") {
10673
+ this._handleCancel();
10674
+ }
10587
10675
  }
10588
10676
  updated(changedProperties) {
10589
10677
  if (changedProperties.has("selectedRoleIds") || changedProperties.has("open")) {
@@ -13027,6 +13115,7 @@ let JupiterDynamicForm = class extends LitElement {
13027
13115
  this._showFactsOnly = false;
13028
13116
  this._conceptSearchText = "";
13029
13117
  this._lastFocusedFieldIdentity = null;
13118
+ this._dimensionColumnDriftDiagnostics = [];
13030
13119
  this._periodPreferences = {};
13031
13120
  this._activeSidePanelRoleId = null;
13032
13121
  this._sidePanelSearchQuery = "";
@@ -15919,6 +16008,7 @@ let JupiterDynamicForm = class extends LitElement {
15919
16008
  });
15920
16009
  this._formData = { ...this._formData, ...restoredFormData };
15921
16010
  console.log(`🔄 Restored ${Object.keys(restoredFormData).length} concepts with data`);
16011
+ this._diagnoseDimensionColumnDrift(formData);
15922
16012
  this._seedLegacyManuallyEditedTotals(restoredFormData);
15923
16013
  this._initializeGlobalDecimalsFromRoundingData();
15924
16014
  this._periodData = {
@@ -16415,6 +16505,70 @@ let JupiterDynamicForm = class extends LitElement {
16415
16505
  }
16416
16506
  return null;
16417
16507
  }
16508
+ /**
16509
+ * JDF-133 Phase 0: diagnostics-only. Walks the raw draft `formData` array `_restoreFromDraft`
16510
+ * was given — not `restoredFormData`, which has already dropped each entry's own `dimension`
16511
+ * string by the time this runs — and flags any entry whose saved `dimension` tag disagrees with
16512
+ * what its `columnId` resolves to in the schema just rebuilt for this session. Read-only:
16513
+ * populates `_dimensionColumnDriftDiagnostics` and logs, never writes to `_formData` or any
16514
+ * other state a render or a later save could observe. Builds a one-time conceptId->section index
16515
+ * (`_allSections` can run to hundreds of sections/thousands of concepts) rather than repeating
16516
+ * `_findSectionForConcept`'s linear scan once per fact, so this stays cheap enough to run
16517
+ * unconditionally on every restore without being itself a measurable behavior change.
16518
+ */
16519
+ _diagnoseDimensionColumnDrift(formData) {
16520
+ var _a, _b, _c;
16521
+ const sectionByConceptId = /* @__PURE__ */ new Map();
16522
+ const indexConcepts = (concepts, section2) => {
16523
+ concepts.forEach((concept) => {
16524
+ var _a2;
16525
+ sectionByConceptId.set(concept.id, section2);
16526
+ if ((_a2 = concept.children) == null ? void 0 : _a2.length)
16527
+ indexConcepts(concept.children, section2);
16528
+ });
16529
+ };
16530
+ this._allSections.forEach((section2) => indexConcepts(section2.concepts, section2));
16531
+ const drifted = [];
16532
+ for (const entry of formData) {
16533
+ const savedDimension = entry == null ? void 0 : entry.dimension;
16534
+ if (!savedDimension || typeof savedDimension !== "string")
16535
+ continue;
16536
+ const section2 = sectionByConceptId.get(entry.conceptId);
16537
+ const column2 = (_a = section2 == null ? void 0 : section2.columns) == null ? void 0 : _a.find((c2) => c2.id === entry.columnId);
16538
+ const currentColumnDimension = (_b = column2 == null ? void 0 : column2.dimensionData) == null ? void 0 : _b.dimensionIdKey;
16539
+ if (!currentColumnDimension)
16540
+ continue;
16541
+ if (!this._dimensionKeysMatch(savedDimension, currentColumnDimension)) {
16542
+ drifted.push({
16543
+ conceptId: entry.conceptId,
16544
+ columnId: entry.columnId,
16545
+ roleURI: ((_c = section2 == null ? void 0 : section2.metadata) == null ? void 0 : _c.roleURI) || (section2 == null ? void 0 : section2.id) || "unknown",
16546
+ value: entry.value,
16547
+ savedDimension,
16548
+ currentColumnDimension
16549
+ });
16550
+ }
16551
+ }
16552
+ this._dimensionColumnDriftDiagnostics = drifted;
16553
+ if (drifted.length > 0) {
16554
+ console.warn(
16555
+ `⚠️ [JDF-133] Detected ${drifted.length} fact(s) whose saved dimension tag no longer matches their column's current dimension — see form._dimensionColumnDriftDiagnostics. Diagnostics-only: nothing was changed or corrected.`
16556
+ );
16557
+ console.table(drifted);
16558
+ }
16559
+ }
16560
+ /** Order-independent comparison of two `axisId|memberId::axisId|memberId...` dimensionIdKey-shaped strings. */
16561
+ _dimensionKeysMatch(a2, b2) {
16562
+ const setA = new Set(a2.split("::").filter(Boolean));
16563
+ const setB = new Set(b2.split("::").filter(Boolean));
16564
+ if (setA.size !== setB.size)
16565
+ return false;
16566
+ for (const item of setA) {
16567
+ if (!setB.has(item))
16568
+ return false;
16569
+ }
16570
+ return true;
16571
+ }
16418
16572
  _findSectionForConcept(conceptId) {
16419
16573
  for (const section2 of this._allSections) {
16420
16574
  const concept = this._findConceptInSection(section2.concepts, conceptId);