jupiter-dynamic-forms 1.21.0 → 1.22.1

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
@@ -1,4 +1,4 @@
1
- import { LitElement, html, css } from "lit";
1
+ import { LitElement, html, nothing, css } from "lit";
2
2
  /**
3
3
  * @license
4
4
  * Copyright 2017 Google LLC
@@ -1740,7 +1740,10 @@ const filter$1 = {
1740
1740
  searchByConceptDescription: "Type concept name or label to filter roles and rows. Press Apply Filter to apply.",
1741
1741
  searchByConceptPlaceholder: "Type concept name or label and press Apply Filter...",
1742
1742
  clearFilters: "Clear Filters",
1743
- resetFilter: "Reset"
1743
+ resetFilter: "Reset",
1744
+ bannerConcept: 'Filtered by "{{concept}}" — {{count}} row(s) visible',
1745
+ bannerFactsOnly: "Only filled-in fields are shown",
1746
+ showAll: "Show all"
1744
1747
  };
1745
1748
  const column$1 = {
1746
1749
  addColumn: "Add Column",
@@ -1764,26 +1767,33 @@ const column$1 = {
1764
1767
  required: "required"
1765
1768
  };
1766
1769
  const section$1 = {
1767
- enterPlaceholder: "Enter"
1770
+ enterPlaceholder: "Enter",
1771
+ position: "Section {{current}} of {{total}}",
1772
+ previous: "← Previous",
1773
+ next: "Next: {{title}} →",
1774
+ locateInList: "Show this section in the list on the left",
1775
+ amountScaleBase: "Amounts in €",
1776
+ amountScaleFactor: "Amounts in € × {{factor}}"
1768
1777
  };
1769
1778
  const field$1 = {
1770
1779
  selectOption: "Select an option...",
1771
1780
  select: "Select...",
1772
- enterDetailedExplanation: "Enter detailed explanation...",
1773
- enterAmountNoDecimals: "Enter amount (no decimals)",
1781
+ emptyOption: "",
1782
+ enterDetailedExplanation: "Detailed explanation",
1783
+ enterAmountNoDecimals: "Amount, no decimals",
1774
1784
  enterCurrency: "0.00",
1775
- enterText: "Enter text",
1776
- enterWholeNumber: "Enter whole number",
1777
- enterPositiveNumber: "Enter positive number",
1778
- enterDecimalValue: "Enter decimal value",
1779
- enterNumberOfShares: "Enter number of shares",
1785
+ enterText: "Text",
1786
+ enterWholeNumber: "Whole number",
1787
+ enterPositiveNumber: "Positive number",
1788
+ enterDecimalValue: "Decimal value",
1789
+ enterNumberOfShares: "Number of shares",
1780
1790
  yearFormat: "YYYY",
1781
1791
  enterPercentage: "e.g. 0.25 for 25% or 0.2550 for 25.50%",
1782
1792
  emailPlaceholder: "email@example.com",
1783
1793
  urlPlaceholder: "https://example.com",
1784
1794
  phonePlaceholder: "+1 (555) 000-0000",
1785
- enterValue: "Enter value",
1786
- enterDetailedInformation: "Enter detailed information...",
1795
+ enterValue: "Value",
1796
+ enterDetailedInformation: "Detailed information",
1787
1797
  scale: "Scale",
1788
1798
  addText: "Add text",
1789
1799
  editText: "Edit",
@@ -1796,7 +1806,12 @@ const field$1 = {
1796
1806
  date: "Date",
1797
1807
  unit: "Unit",
1798
1808
  selectUnit: "Select Unit",
1799
- leaveBlankForInf: "Leave blank for INF"
1809
+ leaveBlankForInf: "Leave blank for INF",
1810
+ prefilledLocked: "Pre-filled — cannot be changed here",
1811
+ invalidNumber: `Can't read "{{value}}" as a number`,
1812
+ noDecimalsAllowed: "This field doesn't accept decimals",
1813
+ negativeNotAllowed: "A negative value isn't allowed here",
1814
+ ambiguousNumber: '"{{value}}" is ambiguous — retype it without the thousands separator'
1800
1815
  };
1801
1816
  const admin$1 = {
1802
1817
  title: "Configure Roles",
@@ -1832,6 +1847,16 @@ const error$1 = {
1832
1847
  ok: "OK"
1833
1848
  }
1834
1849
  };
1850
+ const typedDimensionWarning$1 = {
1851
+ title: "Typed dimension values missing",
1852
+ message: "The form contains errors for the following roles where a typed dimension value is missing. Do you still want to continue?",
1853
+ role: "Role:",
1854
+ field: "Field:",
1855
+ value: "Value:",
1856
+ clickToFocus: "Click to go to this fact",
1857
+ "continue": "Continue to validate",
1858
+ cancel: "Cancel"
1859
+ };
1835
1860
  const calculationWarning$1 = {
1836
1861
  title: "Calculation warning",
1837
1862
  summaryLine: 'Total "{{label}}" should equal {{calculated}}, but you entered {{entered}}.',
@@ -1841,6 +1866,10 @@ const calculationWarning$1 = {
1841
1866
  added: "Added",
1842
1867
  subtracted: "Subtracted (opposite balance)"
1843
1868
  };
1869
+ const balanceCheck$1 = {
1870
+ balanced: "{{column}}: In balance",
1871
+ difference: "Difference {{column}}: {{amount}}"
1872
+ };
1844
1873
  const formulaValidation$1 = {
1845
1874
  dialogTitle: "Formula check results",
1846
1875
  allPassed: "All formula checks passed",
@@ -1869,7 +1898,9 @@ const enTranslations = {
1869
1898
  validation: validation$1,
1870
1899
  xbrlValidation: xbrlValidation$1,
1871
1900
  error: error$1,
1901
+ typedDimensionWarning: typedDimensionWarning$1,
1872
1902
  calculationWarning: calculationWarning$1,
1903
+ balanceCheck: balanceCheck$1,
1873
1904
  formulaValidation: formulaValidation$1,
1874
1905
  scrollToConcept: scrollToConcept$1
1875
1906
  };
@@ -1956,7 +1987,10 @@ const filter = {
1956
1987
  searchByConceptDescription: "Typ een conceptnaam of label om rollen en rijen te filteren. Druk op Filter toepassen om te activeren.",
1957
1988
  searchByConceptPlaceholder: "Typ conceptnaam of label en druk op Filter toepassen...",
1958
1989
  clearFilters: "Filters wissen",
1959
- resetFilter: "Resetten"
1990
+ resetFilter: "Resetten",
1991
+ bannerConcept: 'Gefilterd op "{{concept}}" — {{count}} rij(en) zichtbaar',
1992
+ bannerFactsOnly: "Alleen ingevulde velden worden getoond",
1993
+ showAll: "Alles tonen"
1960
1994
  };
1961
1995
  const column = {
1962
1996
  addColumn: "Kolom toevoegen",
@@ -1980,26 +2014,33 @@ const column = {
1980
2014
  required: "verplicht"
1981
2015
  };
1982
2016
  const section = {
1983
- enterPlaceholder: "Invoeren"
2017
+ enterPlaceholder: "Invoeren",
2018
+ position: "Onderdeel {{current}} van {{total}}",
2019
+ previous: "← Vorige",
2020
+ next: "Volgende: {{title}} →",
2021
+ locateInList: "Toon dit onderdeel in de lijst aan de linkerkant",
2022
+ amountScaleBase: "Bedragen in €",
2023
+ amountScaleFactor: "Bedragen in € × {{factor}}"
1984
2024
  };
1985
2025
  const field = {
1986
2026
  selectOption: "Selecteer een optie...",
1987
2027
  select: "Selecteer...",
1988
- enterDetailedExplanation: "Voer gedetailleerde uitleg in...",
1989
- enterAmountNoDecimals: "Voer bedrag in (geen decimalen)",
2028
+ emptyOption: "",
2029
+ enterDetailedExplanation: "Gedetailleerde uitleg",
2030
+ enterAmountNoDecimals: "Bedrag, zonder decimalen",
1990
2031
  enterCurrency: "0,00",
1991
- enterText: "Voer tekst in",
1992
- enterWholeNumber: "Voer geheel getal in",
1993
- enterPositiveNumber: "Voer positief getal in",
1994
- enterDecimalValue: "Voer decimale waarde in",
1995
- enterNumberOfShares: "Voer aantal aandelen in",
2032
+ enterText: "Tekst",
2033
+ enterWholeNumber: "Geheel getal",
2034
+ enterPositiveNumber: "Positief getal",
2035
+ enterDecimalValue: "Decimale waarde",
2036
+ enterNumberOfShares: "Aantal aandelen",
1996
2037
  yearFormat: "JJJJ",
1997
2038
  enterPercentage: "bijv. 0,25 voor 25% of 0,2550 voor 25,50%",
1998
2039
  emailPlaceholder: "email@voorbeeld.nl",
1999
2040
  urlPlaceholder: "https://voorbeeld.nl",
2000
2041
  phonePlaceholder: "+31 (0)20 123 4567",
2001
- enterValue: "Voer waarde in",
2002
- enterDetailedInformation: "Voer gedetailleerde informatie in...",
2042
+ enterValue: "Waarde",
2043
+ enterDetailedInformation: "Gedetailleerde informatie",
2003
2044
  scale: "Schaal",
2004
2045
  addText: "Tekst toevoegen",
2005
2046
  editText: "Bewerken",
@@ -2012,7 +2053,12 @@ const field = {
2012
2053
  date: "Datum",
2013
2054
  unit: "Eenheid",
2014
2055
  selectUnit: "Selecteer eenheid",
2015
- leaveBlankForInf: "Leeg laten voor INF"
2056
+ leaveBlankForInf: "Leeg laten voor INF",
2057
+ prefilledLocked: "Vooraf ingevuld — kan hier niet worden gewijzigd",
2058
+ invalidNumber: "Kan '{{value}}' niet lezen als getal",
2059
+ noDecimalsAllowed: "Dit veld accepteert geen decimalen",
2060
+ negativeNotAllowed: "Een negatieve waarde is hier niet toegestaan",
2061
+ ambiguousNumber: "'{{value}}' is dubbelzinnig — typ het opnieuw zonder duizendtalscheidingsteken"
2016
2062
  };
2017
2063
  const admin = {
2018
2064
  title: "Rollen configureren",
@@ -2048,6 +2094,16 @@ const error = {
2048
2094
  ok: "OK"
2049
2095
  }
2050
2096
  };
2097
+ const typedDimensionWarning = {
2098
+ title: "Waarden voor typed dimensie ontbreken",
2099
+ message: "Het formulier bevat fouten voor de volgende rollen waar een waarde voor de typed dimensie ontbreekt. Wilt u toch doorgaan?",
2100
+ role: "Rol:",
2101
+ field: "Veld:",
2102
+ value: "Waarde:",
2103
+ clickToFocus: "Klik om naar dit feit te gaan",
2104
+ "continue": "Doorgaan met valideren",
2105
+ cancel: "Annuleren"
2106
+ };
2051
2107
  const calculationWarning = {
2052
2108
  title: "Berekeningswaarschuwing",
2053
2109
  summaryLine: 'Totaal "{{label}}" zou {{calculated}} moeten zijn, maar u heeft {{entered}} ingevoerd.',
@@ -2057,6 +2113,10 @@ const calculationWarning = {
2057
2113
  added: "Opgeteld",
2058
2114
  subtracted: "Afgetrokken (tegengestelde balans)"
2059
2115
  };
2116
+ const balanceCheck = {
2117
+ balanced: "{{column}}: In balans",
2118
+ difference: "Verschil {{column}}: {{amount}}"
2119
+ };
2060
2120
  const formulaValidation = {
2061
2121
  dialogTitle: "Resultaten formulecontrole",
2062
2122
  allPassed: "Alle formulecontroles geslaagd",
@@ -2085,7 +2145,9 @@ const nlTranslations = {
2085
2145
  validation,
2086
2146
  xbrlValidation,
2087
2147
  error,
2148
+ typedDimensionWarning,
2088
2149
  calculationWarning,
2150
+ balanceCheck,
2089
2151
  formulaValidation,
2090
2152
  scrollToConcept
2091
2153
  };
@@ -3969,6 +4031,113 @@ function rankMatchingSections(roleQuery, sections) {
3969
4031
  return [];
3970
4032
  return sections.map((section2, index) => ({ section: section2, index, score: scoreSection(query, section2) })).filter((entry) => entry.score !== null).sort((a2, b2) => a2.score.tier - b2.score.tier || a2.score.looseness - b2.score.looseness || a2.index - b2.index).map(({ section: section2, score }) => ({ section: section2, tier: score.tier }));
3971
4033
  }
4034
+ const ASSETS_CONCEPT_NAME = "jenv-bw2-i:Assets";
4035
+ const EQUITY_AND_LIABILITIES_CONCEPT_NAME = "jenv-bw2-i:EquityAndLiabilities";
4036
+ function findBalanceSheetTotals(concepts) {
4037
+ let assetsConceptId;
4038
+ let equityConceptId;
4039
+ const visit = (nodes) => {
4040
+ var _a, _b;
4041
+ for (const node of nodes) {
4042
+ if (isTotalLabel$1(node.preferredLabel)) {
4043
+ const name = ((_a = node.conceptMetadata) == null ? void 0 : _a.conceptName) ?? node.name;
4044
+ if (name === ASSETS_CONCEPT_NAME)
4045
+ assetsConceptId = node.id;
4046
+ else if (name === EQUITY_AND_LIABILITIES_CONCEPT_NAME)
4047
+ equityConceptId = node.id;
4048
+ }
4049
+ if ((_b = node.children) == null ? void 0 : _b.length)
4050
+ visit(node.children);
4051
+ }
4052
+ };
4053
+ visit(concepts);
4054
+ return assetsConceptId && equityConceptId ? { assetsConceptId, equityConceptId } : null;
4055
+ }
4056
+ function computeBalanceDifference(formData, columns, totals) {
4057
+ var _a, _b;
4058
+ const results = [];
4059
+ for (const column2 of columns) {
4060
+ if (column2.hidden)
4061
+ continue;
4062
+ const assetsValue = parseFloat((_a = formData[totals.assetsConceptId]) == null ? void 0 : _a[column2.id]);
4063
+ const equityValue = parseFloat((_b = formData[totals.equityConceptId]) == null ? void 0 : _b[column2.id]);
4064
+ if (isNaN(assetsValue) || isNaN(equityValue))
4065
+ continue;
4066
+ const difference = assetsValue - equityValue;
4067
+ const columnLabel = column2.description && column2.description !== column2.title ? `${column2.title} ${column2.description}` : column2.title;
4068
+ results.push({
4069
+ columnId: column2.id,
4070
+ columnLabel,
4071
+ assetsValue,
4072
+ equityValue,
4073
+ difference,
4074
+ balanced: Math.abs(difference) < CALCULATION_TOLERANCE
4075
+ });
4076
+ }
4077
+ return results;
4078
+ }
4079
+ function getColumnParts(dimensionData) {
4080
+ var _a, _b;
4081
+ if (!dimensionData)
4082
+ return [];
4083
+ const parts = [];
4084
+ if ((_a = dimensionData.combinations) == null ? void 0 : _a.length) {
4085
+ for (const c2 of dimensionData.combinations) {
4086
+ parts.push({ key: c2.axisId || c2.memberLabel, label: c2.memberLabel });
4087
+ }
4088
+ } else {
4089
+ const selectedDimensions = dimensionData.selectedDimensions;
4090
+ for (const d2 of selectedDimensions ?? []) {
4091
+ if (!d2.isTyped && d2.memberLabel)
4092
+ parts.push({ key: d2.axisId, label: d2.memberLabel });
4093
+ }
4094
+ }
4095
+ if ((_b = dimensionData.typedMembers) == null ? void 0 : _b.length) {
4096
+ for (const t2 of dimensionData.typedMembers) {
4097
+ parts.push({ key: t2.axisId, label: t2.axisLabel });
4098
+ }
4099
+ }
4100
+ if (parts.length === 0 && dimensionData.memberLabel && (dimensionData.axisId || dimensionData.axisLabel)) {
4101
+ parts.push({ key: dimensionData.axisId || dimensionData.axisLabel, label: dimensionData.memberLabel });
4102
+ }
4103
+ return parts;
4104
+ }
4105
+ function splitCommonMembers(columns) {
4106
+ const perColumnLabel = {};
4107
+ const partsByColumnId = /* @__PURE__ */ new Map();
4108
+ for (const column2 of columns) {
4109
+ if (column2.type !== "dimension")
4110
+ continue;
4111
+ const parts = getColumnParts(column2.dimensionData);
4112
+ if (parts.length > 0)
4113
+ partsByColumnId.set(column2.id, parts);
4114
+ }
4115
+ const eligible = columns.filter((c2) => partsByColumnId.has(c2.id));
4116
+ if (eligible.length < 2) {
4117
+ for (const column2 of columns)
4118
+ perColumnLabel[column2.id] = column2.title;
4119
+ return { caption: "", perColumnLabel };
4120
+ }
4121
+ const referenceParts = partsByColumnId.get(eligible[0].id);
4122
+ const commonKeys = new Set(
4123
+ referenceParts.filter(
4124
+ (part) => eligible.every((column2) => {
4125
+ const match = partsByColumnId.get(column2.id).find((p2) => p2.key === part.key);
4126
+ return (match == null ? void 0 : match.label) === part.label;
4127
+ })
4128
+ ).map((part) => part.key)
4129
+ );
4130
+ const caption = referenceParts.filter((part) => commonKeys.has(part.key)).map((part) => part.label).join(" · ");
4131
+ for (const column2 of columns) {
4132
+ const parts = partsByColumnId.get(column2.id);
4133
+ if (!parts) {
4134
+ perColumnLabel[column2.id] = column2.title;
4135
+ continue;
4136
+ }
4137
+ perColumnLabel[column2.id] = parts.filter((part) => !commonKeys.has(part.key)).map((part) => part.label).join(" · ");
4138
+ }
4139
+ return { caption, perColumnLabel };
4140
+ }
3972
4141
  const TYPE_INPUT_MAP = {
3973
4142
  // ==========================================
3974
4143
  // Dutch XBRL Types (nl-types namespace)
@@ -4618,6 +4787,125 @@ class XBRLValidator {
4618
4787
  return rules;
4619
4788
  }
4620
4789
  }
4790
+ function isValidThousandsGrouping(groups) {
4791
+ return groups.length >= 1 && groups[0].length >= 1 && groups[0].length <= 3 && groups.slice(1).every((g) => g.length === 3);
4792
+ }
4793
+ function parseLocaleNumber(raw, options = {}) {
4794
+ const allowDecimals = options.allowDecimals !== false;
4795
+ const allowNegative = options.allowNegative !== false;
4796
+ const isDutch = (options.locale ?? "nl-NL").toLowerCase().startsWith("nl");
4797
+ let s2 = raw.trim();
4798
+ if (s2 === "") {
4799
+ return { ok: false, reason: "unreadable" };
4800
+ }
4801
+ s2 = s2.replace(/EUR/gi, "").replace(/[€'\s]/g, "");
4802
+ if (s2 === "") {
4803
+ return { ok: false, reason: "unreadable" };
4804
+ }
4805
+ let negative = false;
4806
+ const parenMatch = /^\((.*)\)$/.exec(s2);
4807
+ if (parenMatch) {
4808
+ negative = true;
4809
+ s2 = parenMatch[1];
4810
+ }
4811
+ if (s2.startsWith("+")) {
4812
+ s2 = s2.slice(1);
4813
+ } else if (s2.startsWith("-")) {
4814
+ negative = true;
4815
+ s2 = s2.slice(1);
4816
+ } else if (s2.endsWith("-")) {
4817
+ negative = true;
4818
+ s2 = s2.slice(0, -1);
4819
+ }
4820
+ if (s2 === "" || !/^[0-9.,]+$/.test(s2)) {
4821
+ return { ok: false, reason: "unreadable" };
4822
+ }
4823
+ const dotCount = (s2.match(/\./g) || []).length;
4824
+ const commaCount = (s2.match(/,/g) || []).length;
4825
+ let numericString;
4826
+ let isThousandsOnly = false;
4827
+ if (dotCount === 0 && commaCount === 0) {
4828
+ numericString = s2;
4829
+ isThousandsOnly = true;
4830
+ } else if (dotCount > 0 && commaCount > 0) {
4831
+ const lastDot = s2.lastIndexOf(".");
4832
+ const lastComma = s2.lastIndexOf(",");
4833
+ const decimalSep = lastDot > lastComma ? "." : ",";
4834
+ const thousandsSep = decimalSep === "." ? "," : ".";
4835
+ const decimalCount = decimalSep === "." ? dotCount : commaCount;
4836
+ const decimalIndex = decimalSep === "." ? lastDot : lastComma;
4837
+ const thousandsBeforeDecimal = [...s2].every((ch, i2) => ch !== thousandsSep || i2 < decimalIndex);
4838
+ if (decimalCount !== 1 || !thousandsBeforeDecimal) {
4839
+ return { ok: false, reason: "unreadable" };
4840
+ }
4841
+ const intPart = s2.slice(0, decimalIndex);
4842
+ const fracPart = s2.slice(decimalIndex + 1);
4843
+ const groups = intPart.split(thousandsSep);
4844
+ if (!isValidThousandsGrouping(groups) || fracPart.length === 0) {
4845
+ return { ok: false, reason: "unreadable" };
4846
+ }
4847
+ numericString = `${groups.join("")}.${fracPart}`;
4848
+ } else if (dotCount >= 2 || commaCount >= 2) {
4849
+ const sep = dotCount >= 2 ? "." : ",";
4850
+ const groups = s2.split(sep);
4851
+ if (!isValidThousandsGrouping(groups)) {
4852
+ return { ok: false, reason: "unreadable" };
4853
+ }
4854
+ numericString = groups.join("");
4855
+ isThousandsOnly = true;
4856
+ } else {
4857
+ const sep = dotCount === 1 ? "." : ",";
4858
+ const idx = s2.indexOf(sep);
4859
+ const intPart = s2.slice(0, idx);
4860
+ const fracPart = s2.slice(idx + 1);
4861
+ if (intPart === "" || fracPart === "") {
4862
+ return { ok: false, reason: "unreadable" };
4863
+ }
4864
+ const isPlausibleThousandsGroup = fracPart.length === 3 && intPart !== "0";
4865
+ if (isDutch) {
4866
+ if (sep === ".") {
4867
+ if (allowDecimals) {
4868
+ numericString = `${intPart}.${fracPart}`;
4869
+ } else if (isPlausibleThousandsGroup) {
4870
+ numericString = intPart + fracPart;
4871
+ isThousandsOnly = true;
4872
+ } else {
4873
+ numericString = `${intPart}.${fracPart}`;
4874
+ }
4875
+ } else if (isPlausibleThousandsGroup) {
4876
+ if (allowDecimals) {
4877
+ return { ok: false, reason: "ambiguous" };
4878
+ }
4879
+ numericString = intPart + fracPart;
4880
+ isThousandsOnly = true;
4881
+ } else {
4882
+ numericString = `${intPart}.${fracPart}`;
4883
+ }
4884
+ } else if (sep === ".") {
4885
+ numericString = `${intPart}.${fracPart}`;
4886
+ } else if (isPlausibleThousandsGroup) {
4887
+ numericString = intPart + fracPart;
4888
+ isThousandsOnly = true;
4889
+ } else {
4890
+ return { ok: false, reason: "unreadable" };
4891
+ }
4892
+ }
4893
+ if (numericString === null) {
4894
+ return { ok: false, reason: "unreadable" };
4895
+ }
4896
+ const value = Number(numericString);
4897
+ if (!Number.isFinite(value)) {
4898
+ return { ok: false, reason: "unreadable" };
4899
+ }
4900
+ if (negative && !allowNegative) {
4901
+ return { ok: false, reason: "negativeNotAllowed" };
4902
+ }
4903
+ const signedValue = negative ? -value : value;
4904
+ if (!allowDecimals && !isThousandsOnly && signedValue !== Math.trunc(signedValue)) {
4905
+ return { ok: false, reason: "decimalsNotAllowed" };
4906
+ }
4907
+ return { ok: true, value: signedValue };
4908
+ }
4621
4909
  var __defProp$8 = Object.defineProperty;
4622
4910
  var __getOwnPropDesc$8 = Object.getOwnPropertyDescriptor;
4623
4911
  var __decorateClass$8 = (decorators, target, key, kind) => {
@@ -4636,6 +4924,7 @@ let JupiterFormField = class extends LitElement {
4636
4924
  this.value = null;
4637
4925
  this.disabled = false;
4638
4926
  this.locale = "en-US";
4927
+ this.numberLocale = "nl-NL";
4639
4928
  this.hideLabel = false;
4640
4929
  this.mode = "inputForm";
4641
4930
  this.globalDecimals = "INF";
@@ -4646,6 +4935,7 @@ let JupiterFormField = class extends LitElement {
4646
4935
  this._showPeriodPopup = false;
4647
4936
  this._availableUnits = [];
4648
4937
  this._numericDraftValue = null;
4938
+ this._focused = false;
4649
4939
  }
4650
4940
  willUpdate(changedProperties) {
4651
4941
  var _a;
@@ -4687,6 +4977,20 @@ let JupiterFormField = class extends LitElement {
4687
4977
  _isRoundingLevelConcept() {
4688
4978
  return !!this.conceptId && this.conceptId.includes("DocumentIntendedRoundingLevel");
4689
4979
  }
4980
+ /**
4981
+ * JDF-110: lock icon (SVG, not emoji) shown on a masterData-prefilled or rounding-level-locked
4982
+ * cell, so a locked value reads as "filled and protected" rather than empty placeholder text.
4983
+ * The host's `prefilledTooltip` overrides the default hint text.
4984
+ */
4985
+ _renderLockIcon() {
4986
+ const tooltip = this.prefilledTooltip || I18n.t("field.prefilledLocked");
4987
+ return html`
4988
+ <svg class="lock-icon" viewBox="0 0 24 24" role="img" aria-label="${tooltip}">
4989
+ <title>${tooltip}</title>
4990
+ <path fill="currentColor" d="M12 1a5 5 0 0 0-5 5v3H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V11a2 2 0 0 0-2-2h-1V6a5 5 0 0 0-5-5zm-3 8V6a3 3 0 0 1 6 0v3z"/>
4991
+ </svg>
4992
+ `;
4993
+ }
4690
4994
  _isNumericField() {
4691
4995
  var _a;
4692
4996
  const typeConfig = getInputTypeForConceptType(this.conceptType, this.datatypes);
@@ -4694,7 +4998,32 @@ let JupiterFormField = class extends LitElement {
4694
4998
  return isNumericType(effectiveFieldType) || this._getInputType() === "number";
4695
4999
  }
4696
5000
  _isIntermediateNumericValue(raw) {
4697
- return raw === "-" || raw === "." || raw === "-.";
5001
+ return raw === "-" || raw === "." || raw === "-." || raw === "," || raw === "-,";
5002
+ }
5003
+ // JDF-091: the same allowDecimals/allowNegative resolution _handleKeyDown already computes,
5004
+ // shared with the parser so every code path agrees on what's allowed for this field.
5005
+ _getNumberParseOptions() {
5006
+ const typeConfig = getInputTypeForConceptType(this.conceptType, this.datatypes);
5007
+ const allowNegative = typeConfig.min === void 0 || typeof typeConfig.min === "number" && typeConfig.min < 0;
5008
+ return {
5009
+ locale: this.numberLocale,
5010
+ allowDecimals: typeConfig.allowDecimals !== false,
5011
+ allowNegative
5012
+ };
5013
+ }
5014
+ // JDF-091: maps a parse failure reason to the field's inline error message.
5015
+ _numberParseErrorMessage(reason, raw) {
5016
+ switch (reason) {
5017
+ case "decimalsNotAllowed":
5018
+ return I18n.t("field.noDecimalsAllowed");
5019
+ case "negativeNotAllowed":
5020
+ return I18n.t("field.negativeNotAllowed");
5021
+ case "ambiguous":
5022
+ return I18n.t("field.ambiguousNumber", { value: raw });
5023
+ case "unreadable":
5024
+ default:
5025
+ return I18n.t("field.invalidNumber", { value: raw });
5026
+ }
4698
5027
  }
4699
5028
  _handleInput(event) {
4700
5029
  const target = event.target;
@@ -4714,8 +5043,8 @@ let JupiterFormField = class extends LitElement {
4714
5043
  if (rawValue === "" || this._isIntermediateNumericValue(rawValue)) {
4715
5044
  value = null;
4716
5045
  } else {
4717
- const parsed = Number(rawValue);
4718
- value = Number.isNaN(parsed) ? null : parsed;
5046
+ const parsed = parseLocaleNumber(rawValue, this._getNumberParseOptions());
5047
+ value = parsed.ok ? parsed.value : null;
4719
5048
  }
4720
5049
  } else if (this.field.type === "boolean") {
4721
5050
  value = target.checked;
@@ -4737,6 +5066,7 @@ let JupiterFormField = class extends LitElement {
4737
5066
  }));
4738
5067
  }
4739
5068
  _handleFocus() {
5069
+ this._focused = true;
4740
5070
  this.dispatchEvent(new CustomEvent("field-focus", {
4741
5071
  detail: {
4742
5072
  fieldId: this.field.id,
@@ -4747,17 +5077,25 @@ let JupiterFormField = class extends LitElement {
4747
5077
  }));
4748
5078
  }
4749
5079
  _handleBlur() {
5080
+ this._focused = false;
4750
5081
  let userTypedNewValue = false;
5082
+ let numberParseError = null;
4751
5083
  if (this._isNumericField() && this._numericDraftValue !== null) {
4752
5084
  userTypedNewValue = true;
4753
5085
  const raw = this._numericDraftValue;
4754
5086
  if (raw === "" || this._isIntermediateNumericValue(raw)) {
4755
5087
  this.value = null;
5088
+ this._numericDraftValue = null;
4756
5089
  } else {
4757
- const parsed = Number(raw);
4758
- this.value = Number.isNaN(parsed) ? null : parsed;
5090
+ const parsed = parseLocaleNumber(raw, this._getNumberParseOptions());
5091
+ if (parsed.ok) {
5092
+ this.value = parsed.value;
5093
+ this._numericDraftValue = null;
5094
+ } else {
5095
+ this.value = null;
5096
+ numberParseError = { type: "invalidNumber", message: this._numberParseErrorMessage(parsed.reason, raw) };
5097
+ }
4759
5098
  }
4760
- this._numericDraftValue = null;
4761
5099
  }
4762
5100
  if (this._isPercentItemType() && userTypedNewValue && this.value !== null && this.value !== void 0) {
4763
5101
  const numVal = Number(this.value);
@@ -4773,6 +5111,9 @@ let JupiterFormField = class extends LitElement {
4773
5111
  this._touched = true;
4774
5112
  this._validateXBRLDatatype();
4775
5113
  this._validateUnitSelection();
5114
+ if (numberParseError) {
5115
+ this._xbrlErrors = [numberParseError, ...this._xbrlErrors];
5116
+ }
4776
5117
  console.log(`🟦 [FormField] Blur event - fieldId: ${this.field.id}, conceptId: ${this.conceptId}, columnId: ${this.columnId}`);
4777
5118
  console.log(`🟦 [FormField] Current _xbrlErrors:`, this._xbrlErrors);
4778
5119
  console.log(`🟦 [FormField] Current value:`, this.value);
@@ -4850,6 +5191,9 @@ let JupiterFormField = class extends LitElement {
4850
5191
  if (event.key === "." && typeConfig.allowDecimals !== false && !target.value.includes(".")) {
4851
5192
  return;
4852
5193
  }
5194
+ if (event.key === "," || event.key === "€" || event.key === "(" || event.key === ")" || event.key === " ") {
5195
+ return;
5196
+ }
4853
5197
  if (event.key >= "0" && event.key <= "9") {
4854
5198
  return;
4855
5199
  }
@@ -5037,6 +5381,10 @@ let JupiterFormField = class extends LitElement {
5037
5381
  max-width: 600px; width: 90vw;
5038
5382
  box-shadow: 0 8px 32px rgba(0,0,0,0.2); overflow: hidden;
5039
5383
  font-family: inherit;
5384
+ /* JDF-118: don't rely on the UA's default margin:auto centering — a host page's
5385
+ * { margin: 0 } reset wins over it and pins the dialog to the top-left corner.
5386
+ Position explicitly instead, independent of any margin. */
5387
+ position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); margin: 0;
5040
5388
  }
5041
5389
  dialog.jdf-text-entry-dialog::backdrop { background: rgba(0,0,0,0.35); }
5042
5390
  .jdf-text-dialog-header {
@@ -5057,7 +5405,9 @@ let JupiterFormField = class extends LitElement {
5057
5405
  .jdf-text-dialog-close:hover { background: #ddd; color: #333; }
5058
5406
  .jdf-text-dialog-body { padding: 20px 20px 12px; }
5059
5407
  .jdf-text-dialog-textarea {
5060
- width: 100%; min-height: 200px; max-height: 50vh;
5408
+ /* JDF-118: at least 60vh tall by default, growing further with content (up to 75vh,
5409
+ see the input listener below), instead of the old cramped 200px/50vh cap. */
5410
+ width: 100%; min-height: 60vh; max-height: 75vh;
5061
5411
  padding: 10px 12px; font-size: 14px; font-family: inherit;
5062
5412
  border: 1px solid #ddd; border-radius: 4px;
5063
5413
  box-sizing: border-box; resize: vertical; line-height: 1.5;
@@ -5115,16 +5465,25 @@ let JupiterFormField = class extends LitElement {
5115
5465
  </div>
5116
5466
  `;
5117
5467
  const cleanTitle = fieldLabel;
5118
- dialog.querySelector(".jdf-text-dialog-title").textContent = cleanTitle || fieldLabel;
5468
+ const titleEl = dialog.querySelector(".jdf-text-dialog-title");
5469
+ titleEl.textContent = cleanTitle || fieldLabel;
5470
+ titleEl.title = cleanTitle || fieldLabel;
5119
5471
  dialog.querySelector(".jdf-text-dialog-cancel").textContent = I18n.t("field.cancel");
5120
5472
  dialog.querySelector(".jdf-text-dialog-save").textContent = I18n.t("field.save");
5121
5473
  const textarea = dialog.querySelector(".jdf-text-dialog-textarea");
5122
5474
  const charCount = dialog.querySelector(".jdf-text-dialog-char-count");
5123
5475
  textarea.value = currentValue;
5124
5476
  charCount.textContent = `${currentValue.length} ${I18n.t("field.characters")}`;
5477
+ const growTextarea = () => {
5478
+ textarea.style.height = "auto";
5479
+ const maxPx = window.innerHeight * 0.75;
5480
+ textarea.style.height = `${Math.min(textarea.scrollHeight, maxPx)}px`;
5481
+ };
5125
5482
  textarea.addEventListener("input", () => {
5126
5483
  charCount.textContent = `${textarea.value.length} ${I18n.t("field.characters")}`;
5484
+ growTextarea();
5127
5485
  });
5486
+ growTextarea();
5128
5487
  dialog.querySelector(".jdf-text-dialog-close").addEventListener("click", () => dialog.close());
5129
5488
  dialog.querySelector(".jdf-text-dialog-cancel").addEventListener("click", () => dialog.close());
5130
5489
  dialog.querySelector(".jdf-text-dialog-save").addEventListener("click", () => {
@@ -5149,13 +5508,14 @@ let JupiterFormField = class extends LitElement {
5149
5508
  dialog.showModal();
5150
5509
  textarea.focus();
5151
5510
  }
5152
- _renderTextDialogTrigger(effectiveValue, effectiveDisabled) {
5511
+ _renderTextDialogTrigger(effectiveValue, effectiveDisabled, isLocked = false) {
5153
5512
  const hasValue = effectiveValue !== null && effectiveValue !== void 0 && String(effectiveValue).trim() !== "";
5154
5513
  const displayText = hasValue ? String(effectiveValue) : "";
5155
5514
  if (effectiveDisabled) {
5156
5515
  return html`
5157
- <span class="text-readonly-label ${hasValue ? "" : "empty"}"
5516
+ <span class="text-readonly-label ${hasValue ? "" : "empty"} ${isLocked ? "locked" : ""}"
5158
5517
  title="${displayText}">${hasValue ? displayText : "—"}</span>
5518
+ ${isLocked ? this._renderLockIcon() : ""}
5159
5519
  `;
5160
5520
  }
5161
5521
  if (!hasValue) {
@@ -5166,7 +5526,8 @@ let JupiterFormField = class extends LitElement {
5166
5526
  var _a;
5167
5527
  return this._openTextDialog("", ((_a = this.field) == null ? void 0 : _a.label) || "");
5168
5528
  }}">
5169
- + ${I18n.t("field.addText")}
5529
+ <span class="text-trigger-plus" aria-hidden="true">+</span>
5530
+ <span class="text-trigger-label">${I18n.t("field.addText")}</span>
5170
5531
  </button>
5171
5532
  `;
5172
5533
  }
@@ -5259,14 +5620,14 @@ let JupiterFormField = class extends LitElement {
5259
5620
  const isMonetary = this._isMonetaryType();
5260
5621
  return html`<span class="readonly-value ${isEmpty ? "empty" : ""} ${isMonetary ? "monetary" : ""}">${isEmpty ? "—" : displayValue}</span>`;
5261
5622
  }
5262
- _renderInput(effectiveValue = this.value, effectiveDisabled = this.disabled) {
5623
+ _renderInput(effectiveValue = this.value, effectiveDisabled = this.disabled, isLocked = false) {
5263
5624
  if (this.mode === "readonly") {
5264
5625
  return this._renderReadonlyValue(effectiveValue);
5265
5626
  }
5266
5627
  const hasErrors = this._errors.some((e2) => e2.severity === "error");
5267
5628
  const hasWarnings = this._errors.some((e2) => e2.severity === "warning");
5268
5629
  const isMonetary = this._isMonetaryType();
5269
- const cssClass = `field-input ${hasErrors ? "error" : hasWarnings ? "warning" : ""} ${isMonetary ? "monetary" : ""}`;
5630
+ const cssClass = `field-input ${hasErrors ? "error" : hasWarnings ? "warning" : ""} ${isMonetary ? "monetary" : ""} ${isLocked ? "locked" : ""}`;
5270
5631
  const fieldId = `${this.conceptId}__${this.columnId}`;
5271
5632
  const fieldName = `data[${this.conceptId}][${this.columnId}]`;
5272
5633
  const typeConfig = getInputTypeForConceptType(this.conceptType, this.datatypes);
@@ -5274,7 +5635,7 @@ let JupiterFormField = class extends LitElement {
5274
5635
  ;
5275
5636
  const effectiveFieldType = typeConfig.fieldType || this.field.type || "text";
5276
5637
  if (effectiveFieldType === "textarea" || isTextareaType(effectiveFieldType)) {
5277
- return this._renderTextDialogTrigger(effectiveValue, effectiveDisabled || this.field.disabled || false);
5638
+ return this._renderTextDialogTrigger(effectiveValue, effectiveDisabled || this.field.disabled || false, isLocked);
5278
5639
  }
5279
5640
  if (effectiveFieldType === "select" || this.field.type === "select") {
5280
5641
  const selectOptions = typeConfig.enumerations || this.field.options || [];
@@ -5289,7 +5650,7 @@ let JupiterFormField = class extends LitElement {
5289
5650
  @focus="${this._handleFocus}"
5290
5651
  @blur="${this._handleBlur}"
5291
5652
  >
5292
- <option value="">${typeConfig.placeholder || I18n.t("field.select")}</option>
5653
+ <option value="">${I18n.t("field.emptyOption")}</option>
5293
5654
  ${selectOptions.map((option) => {
5294
5655
  const optionValue = option.value;
5295
5656
  const optionLabel = option.label || option.value;
@@ -5337,6 +5698,9 @@ let JupiterFormField = class extends LitElement {
5337
5698
  } else {
5338
5699
  renderedInputValue = effectiveValue ?? "";
5339
5700
  }
5701
+ const hintText = typeConfig.placeholder || this.field.placeholder || "";
5702
+ const hintId = `${fieldId}-hint`;
5703
+ const hintSpan = hintText ? html`<span id="${hintId}" class="field-hint-sr-only">${hintText}</span>` : "";
5340
5704
  if (inputType === "date" || inputType === "datetime-local") {
5341
5705
  return html`
5342
5706
  <div class="date-wrapper">
@@ -5348,7 +5712,8 @@ let JupiterFormField = class extends LitElement {
5348
5712
  class="${cssClass}"
5349
5713
  .value="${renderedInputValue}"
5350
5714
  ?disabled="${effectiveDisabled || this.field.disabled}"
5351
- placeholder="${typeConfig.placeholder || this.field.placeholder || ""}"
5715
+ placeholder="${this._focused ? hintText : ""}"
5716
+ aria-describedby="${hintText ? hintId : nothing}"
5352
5717
  min="${min !== void 0 ? min : ""}"
5353
5718
  max="${max !== void 0 ? max : ""}"
5354
5719
  @input="${this._handleInput}"
@@ -5361,6 +5726,7 @@ let JupiterFormField = class extends LitElement {
5361
5726
  a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14
5362
5727
  a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 16H5V9h14z"/>
5363
5728
  </svg>
5729
+ ${hintSpan}
5364
5730
  </div>
5365
5731
  `;
5366
5732
  }
@@ -5373,7 +5739,8 @@ let JupiterFormField = class extends LitElement {
5373
5739
  class="${cssClass}"
5374
5740
  .value="${renderedInputValue}"
5375
5741
  ?disabled="${effectiveDisabled || this.field.disabled}"
5376
- placeholder="${typeConfig.placeholder || this.field.placeholder || ""}"
5742
+ placeholder="${this._focused ? hintText : ""}"
5743
+ aria-describedby="${hintText ? hintId : nothing}"
5377
5744
  step="${step !== void 0 ? step : ""}"
5378
5745
  min="${min !== void 0 ? min : ""}"
5379
5746
  max="${max !== void 0 ? max : ""}"
@@ -5382,7 +5749,7 @@ let JupiterFormField = class extends LitElement {
5382
5749
  @focus="${this._handleFocus}"
5383
5750
  @blur="${this._handleBlur}"
5384
5751
  @keydown="${this._handleKeyDown}"
5385
- />
5752
+ />${hintSpan}
5386
5753
  `;
5387
5754
  }
5388
5755
  _getInputType() {
@@ -5485,9 +5852,18 @@ let JupiterFormField = class extends LitElement {
5485
5852
  }
5486
5853
  return html``;
5487
5854
  }
5855
+ // JDF-118: the period popup is a plain Lit overlay, not a native <dialog>, so it doesn't get
5856
+ // Escape-to-close for free the way the info/text dialogs do. Keydown on the overlay catches it
5857
+ // as it bubbles up from whichever control inside the popup has focus.
5858
+ _handlePeriodPopupKeydown(e2) {
5859
+ if (e2.key === "Escape") {
5860
+ e2.stopPropagation();
5861
+ this._closePeriodPopup();
5862
+ }
5863
+ }
5488
5864
  _renderPeriodPopup() {
5489
5865
  return html`
5490
- <div class="period-popup-overlay" @click="${this._handlePopupOverlayClick}">
5866
+ <div class="period-popup-overlay" @click="${this._handlePopupOverlayClick}" @keydown="${this._handlePeriodPopupKeydown}">
5491
5867
  <div class="period-popup" @click="${(e2) => e2.stopPropagation()}">
5492
5868
  <div class="period-popup-header">
5493
5869
  <div class="period-popup-title">${I18n.t("field.editPeriod")}</div>
@@ -5676,6 +6052,10 @@ let JupiterFormField = class extends LitElement {
5676
6052
  const hasUserValue = this.value !== null && this.value !== void 0;
5677
6053
  const effectiveValue = isPredefinedValue ? this.masterData[baseConceptId] : hasUserValue ? this.value : factValue;
5678
6054
  const effectiveDisabled = isPredefinedValue || isRoundingLevelLocked || this.disabled || this.mode === "readonly";
6055
+ const isLocked = (isPredefinedValue || isRoundingLevelLocked) && this.mode !== "readonly";
6056
+ const typeConfigForLock = getInputTypeForConceptType(this.conceptType, this.datatypes);
6057
+ const effectiveFieldTypeForLock = typeConfigForLock.fieldType || this.field.type || "text";
6058
+ const isTextareaField = effectiveFieldTypeForLock === "textarea" || isTextareaType(effectiveFieldTypeForLock);
5679
6059
  return html`
5680
6060
  <div class="field-container">
5681
6061
  ${showLabel ? html`
@@ -5683,23 +6063,28 @@ let JupiterFormField = class extends LitElement {
5683
6063
  ${this.field.label}
5684
6064
  </label>
5685
6065
  ` : ""}
5686
-
6066
+
5687
6067
  <div class="field-wrapper">
5688
- ${this._renderInput(effectiveValue, effectiveDisabled)}
5689
-
5690
- ${hasPeriodControl && this.mode !== "readonly" && !isRoundingLevelLocked ? html`
5691
- <button
5692
- class="period-icon-btn"
5693
- type="button"
5694
- tabindex="-1"
5695
- @click="${this._handlePeriodIconClick}"
5696
- title="Edit period"
5697
- >
5698
- 📅
5699
- </button>
6068
+ ${this._renderInput(effectiveValue, effectiveDisabled, isLocked)}
6069
+
6070
+ ${isLocked && !isTextareaField || hasPeriodControl && this.mode !== "readonly" && !isRoundingLevelLocked ? html`
6071
+ <div class="field-trailing-icons">
6072
+ ${isLocked && !isTextareaField ? this._renderLockIcon() : ""}
6073
+ ${hasPeriodControl && this.mode !== "readonly" && !isRoundingLevelLocked ? html`
6074
+ <button
6075
+ class="period-icon-btn"
6076
+ type="button"
6077
+ tabindex="-1"
6078
+ @click="${this._handlePeriodIconClick}"
6079
+ title="Edit period"
6080
+ >
6081
+ 📅
6082
+ </button>
6083
+ ` : ""}
6084
+ </div>
5700
6085
  ` : ""}
5701
6086
  </div>
5702
-
6087
+
5703
6088
  ${this._errors.length > 0 && this._touched ? html`
5704
6089
  <div class="field-errors">
5705
6090
  ${this._errors.map((error2) => html`
@@ -5762,6 +6147,25 @@ JupiterFormField.styles = css`
5762
6147
  overflow: hidden;
5763
6148
  }
5764
6149
 
6150
+ /* JDF-110: holds the lock icon and/or the period icon together on the wrapper's right edge
6151
+ (same rail regardless of the sibling input's own width — the date-wrapper next to it sizes
6152
+ to its dd/mm/yyyy content, which is narrower than a full-width text/select input). Only one
6153
+ auto-margin is needed to push the whole group right, however many icons it contains. */
6154
+ .field-trailing-icons {
6155
+ display: flex;
6156
+ align-items: center;
6157
+ gap: 2px;
6158
+ margin-left: auto;
6159
+ flex-shrink: 0;
6160
+ }
6161
+
6162
+ .lock-icon {
6163
+ width: 15px;
6164
+ height: 15px;
6165
+ color: var(--jupiter-locked-text, #6b6b6b);
6166
+ flex-shrink: 0;
6167
+ }
6168
+
5765
6169
  .period-icon-btn {
5766
6170
  background: transparent;
5767
6171
  border: none;
@@ -5774,10 +6178,6 @@ JupiterFormField.styles = css`
5774
6178
  border-radius: 4px;
5775
6179
  transition: background-color 0.2s;
5776
6180
  opacity: 0.7;
5777
- /* Keep this icon on the wrapper's right edge (same rail as every other field type)
5778
- regardless of the sibling input's own width — the date-wrapper next to it sizes to
5779
- its dd/mm/yyyy content, which is narrower than a full-width text/select input. */
5780
- margin-left: auto;
5781
6181
  flex-shrink: 0;
5782
6182
  }
5783
6183
 
@@ -5965,6 +6365,21 @@ JupiterFormField.styles = css`
5965
6365
  cursor: not-allowed;
5966
6366
  }
5967
6367
 
6368
+ /* JDF-109: screen-reader-only hint — the visible placeholder only shows on focus, but a
6369
+ screen-reader user isn't shown "this cell looks empty" the way sighted users are, so the
6370
+ hint stays available via aria-describedby regardless of focus. */
6371
+ .field-hint-sr-only {
6372
+ position: absolute;
6373
+ width: 1px;
6374
+ height: 1px;
6375
+ padding: 0;
6376
+ margin: -1px;
6377
+ overflow: hidden;
6378
+ clip: rect(0, 0, 0, 0);
6379
+ white-space: nowrap;
6380
+ border: 0;
6381
+ }
6382
+
5968
6383
  .field-input {
5969
6384
  width: 100%;
5970
6385
  padding: 6px 8px; /* Reduced padding for table cells */
@@ -5989,6 +6404,18 @@ JupiterFormField.styles = css`
5989
6404
  cursor: not-allowed;
5990
6405
  }
5991
6406
 
6407
+ /* JDF-110: a locked (masterData-prefilled / rounding-level) value must not look like the
6408
+ greyed-out placeholder text a generic :disabled input gets — normal dark text on a subtly
6409
+ tinted background instead, so it clearly reads as "filled" rather than "empty". Two classes
6410
+ + :disabled beats the plain :disabled rule above on specificity. */
6411
+ .field-input.locked:disabled {
6412
+ /* !important: .field-input itself forces background: transparent !important, which would
6413
+ otherwise always win over this tint regardless of selector specificity. */
6414
+ background: var(--jupiter-locked-background, #eef2f7) !important;
6415
+ color: var(--jupiter-locked-text, #333);
6416
+ cursor: default;
6417
+ }
6418
+
5992
6419
  .field-input.error {
5993
6420
  border-color: var(--jupiter-error-color, #d32f2f);
5994
6421
  }
@@ -6101,26 +6528,52 @@ JupiterFormField.styles = css`
6101
6528
  border-color: var(--jupiter-primary-color, #1976d2);
6102
6529
  }
6103
6530
 
6104
- /* Text dialog trigger — empty state */
6531
+ /* JDF-109: Text dialog trigger — empty state. A subtle "+" at rest (no dashed box, since an
6532
+ empty cell should look empty) that expands to the full "Add text" label on hover/focus,
6533
+ so the affordance is discoverable without every empty text cell shouting for attention. */
6105
6534
  .text-trigger-btn {
6535
+ display: inline-flex;
6536
+ align-items: center;
6106
6537
  flex: 1;
6107
6538
  min-width: 0;
6108
6539
  padding: 5px 8px;
6109
- border: 1px dashed var(--jupiter-border-color, #bbb);
6540
+ border: 1px solid transparent;
6110
6541
  border-radius: 4px;
6111
6542
  background: transparent;
6112
- color: var(--jupiter-text-secondary, #888);
6113
- font-size: 12px;
6543
+ color: var(--jupiter-text-secondary, #999);
6544
+ font-size: 13px;
6114
6545
  cursor: pointer;
6115
- text-align: center;
6116
6546
  font-family: inherit;
6117
6547
  box-sizing: border-box;
6118
6548
  }
6119
6549
 
6120
- .text-trigger-btn:hover {
6550
+ .text-trigger-plus {
6551
+ font-size: 14px;
6552
+ line-height: 1;
6553
+ }
6554
+
6555
+ .text-trigger-label {
6556
+ display: inline-block;
6557
+ max-width: 0;
6558
+ overflow: hidden;
6559
+ white-space: nowrap;
6560
+ opacity: 0;
6561
+ transition: max-width 0.15s ease, opacity 0.15s ease, margin-left 0.15s ease;
6562
+ }
6563
+
6564
+ .text-trigger-btn:hover,
6565
+ .text-trigger-btn:focus-visible {
6121
6566
  border-color: var(--jupiter-primary-color, #1976d2);
6122
6567
  color: var(--jupiter-primary-color, #1976d2);
6123
6568
  background: rgba(25, 118, 210, 0.04);
6569
+ outline: none;
6570
+ }
6571
+
6572
+ .text-trigger-btn:hover .text-trigger-label,
6573
+ .text-trigger-btn:focus-visible .text-trigger-label {
6574
+ max-width: 160px;
6575
+ opacity: 1;
6576
+ margin-left: 6px;
6124
6577
  }
6125
6578
 
6126
6579
  /* Text dialog trigger — filled state */
@@ -6179,6 +6632,15 @@ JupiterFormField.styles = css`
6179
6632
  min-width: 0;
6180
6633
  }
6181
6634
 
6635
+ /* JDF-110: same treatment as .field-input.locked, for masterData-prefilled/rounding-level
6636
+ text-block cells (rendered as a plain label rather than an <input>). */
6637
+ .text-readonly-label.locked {
6638
+ background: var(--jupiter-locked-background, #eef2f7);
6639
+ color: var(--jupiter-locked-text, #333);
6640
+ padding: 5px 8px;
6641
+ border-radius: 4px;
6642
+ }
6643
+
6182
6644
  .text-readonly-label.empty {
6183
6645
  color: var(--jupiter-text-secondary, #999);
6184
6646
  font-style: italic;
@@ -6220,6 +6682,9 @@ __decorateClass$8([
6220
6682
  __decorateClass$8([
6221
6683
  n2({ type: String })
6222
6684
  ], JupiterFormField.prototype, "locale", 2);
6685
+ __decorateClass$8([
6686
+ n2({ type: String })
6687
+ ], JupiterFormField.prototype, "numberLocale", 2);
6223
6688
  __decorateClass$8([
6224
6689
  n2({ type: Boolean })
6225
6690
  ], JupiterFormField.prototype, "hideLabel", 2);
@@ -6229,6 +6694,9 @@ __decorateClass$8([
6229
6694
  __decorateClass$8([
6230
6695
  n2({ type: Object })
6231
6696
  ], JupiterFormField.prototype, "masterData", 2);
6697
+ __decorateClass$8([
6698
+ n2({ type: String })
6699
+ ], JupiterFormField.prototype, "prefilledTooltip", 2);
6232
6700
  __decorateClass$8([
6233
6701
  n2({ type: Array })
6234
6702
  ], JupiterFormField.prototype, "facts", 2);
@@ -6274,6 +6742,9 @@ __decorateClass$8([
6274
6742
  __decorateClass$8([
6275
6743
  r()
6276
6744
  ], JupiterFormField.prototype, "_numericDraftValue", 2);
6745
+ __decorateClass$8([
6746
+ r()
6747
+ ], JupiterFormField.prototype, "_focused", 2);
6277
6748
  JupiterFormField = __decorateClass$8([
6278
6749
  t$1("jupiter-form-field")
6279
6750
  ], JupiterFormField);
@@ -6300,6 +6771,7 @@ let JupiterConceptTree = class extends LitElement {
6300
6771
  this.defaultUnits = [];
6301
6772
  this.disabled = false;
6302
6773
  this.locale = "en-US";
6774
+ this.numberLocale = "nl-NL";
6303
6775
  this.expandedConcepts = /* @__PURE__ */ new Set();
6304
6776
  this.mode = "inputForm";
6305
6777
  this.typedMemberData = {};
@@ -6396,6 +6868,10 @@ let JupiterConceptTree = class extends LitElement {
6396
6868
  border: none; border-radius: 8px; padding: 0;
6397
6869
  max-width: 480px; width: 90vw;
6398
6870
  box-shadow: 0 8px 32px rgba(0,0,0,0.2); overflow: hidden;
6871
+ /* JDF-118: don't rely on the UA's default margin:auto centering — a host page's
6872
+ * { margin: 0 } reset (index.html has one) wins over it and pins the dialog to the
6873
+ top-left corner. Position explicitly instead, independent of any margin. */
6874
+ position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); margin: 0;
6399
6875
  }
6400
6876
  dialog.jdf-concept-info-dialog::backdrop { background: rgba(0,0,0,0.35); }
6401
6877
  .jdf-dialog-header {
@@ -6488,6 +6964,8 @@ let JupiterConceptTree = class extends LitElement {
6488
6964
  border: none; border-radius: 8px; padding: 0;
6489
6965
  max-width: 480px; width: 90vw;
6490
6966
  box-shadow: 0 8px 32px rgba(0,0,0,0.2); overflow: hidden;
6967
+ /* JDF-118: see jdf-concept-info-dialog above — explicit centering, immune to host margin resets. */
6968
+ position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); margin: 0;
6491
6969
  }
6492
6970
  dialog.jdf-calc-warning-dialog::backdrop { background: rgba(0,0,0,0.35); }
6493
6971
  .jdf-calc-warning-header {
@@ -6625,9 +7103,11 @@ let JupiterConceptTree = class extends LitElement {
6625
7103
  .periodInstantDate="${field2.periodInstantDate || (field2.periodType === "instant" ? field2.periodEndDate || field2.periodStartDate : void 0)}"
6626
7104
  .disabled="${this.disabled}"
6627
7105
  .locale="${this.locale}"
7106
+ .numberLocale="${this.numberLocale}"
6628
7107
  .hideLabel="${true}"
6629
7108
  .mode="${this.mode}"
6630
7109
  .masterData="${this.masterData}"
7110
+ .prefilledTooltip="${this.prefilledTooltip}"
6631
7111
  .facts="${this.facts}"
6632
7112
  .column="${column2}"
6633
7113
  .typedMemberValues="${this.typedMemberData[`${this.concept.id}__${column2.id}`] || {}}"
@@ -6972,6 +7452,9 @@ __decorateClass$7([
6972
7452
  __decorateClass$7([
6973
7453
  n2({ type: String })
6974
7454
  ], JupiterConceptTree.prototype, "locale", 2);
7455
+ __decorateClass$7([
7456
+ n2({ type: String })
7457
+ ], JupiterConceptTree.prototype, "numberLocale", 2);
6975
7458
  __decorateClass$7([
6976
7459
  n2({ type: Set })
6977
7460
  ], JupiterConceptTree.prototype, "expandedConcepts", 2);
@@ -6984,6 +7467,9 @@ __decorateClass$7([
6984
7467
  __decorateClass$7([
6985
7468
  n2({ type: Object })
6986
7469
  ], JupiterConceptTree.prototype, "masterData", 2);
7470
+ __decorateClass$7([
7471
+ n2({ type: String })
7472
+ ], JupiterConceptTree.prototype, "prefilledTooltip", 2);
6987
7473
  __decorateClass$7([
6988
7474
  n2({ type: Array })
6989
7475
  ], JupiterConceptTree.prototype, "facts", 2);
@@ -7827,6 +8313,7 @@ let JupiterFormSection = class extends LitElement {
7827
8313
  this.disabled = false;
7828
8314
  this.collapsible = true;
7829
8315
  this.locale = "en-US";
8316
+ this.numberLocale = "nl-NL";
7830
8317
  this.isFirstSection = false;
7831
8318
  this.availableDimensions = [];
7832
8319
  this.hideHeader = false;
@@ -7858,6 +8345,7 @@ let JupiterFormSection = class extends LitElement {
7858
8345
  this._highlightMap = /* @__PURE__ */ new Map();
7859
8346
  this._focusedConceptId = null;
7860
8347
  this._calculationMismatches = /* @__PURE__ */ new Map();
8348
+ this._balanceCheckTotals = null;
7861
8349
  this._totalChildrenMap = /* @__PURE__ */ new Map();
7862
8350
  this._memberParentMap = /* @__PURE__ */ new Map();
7863
8351
  this._totalConceptMap = /* @__PURE__ */ new Map();
@@ -7877,6 +8365,13 @@ let JupiterFormSection = class extends LitElement {
7877
8365
  document.removeEventListener("click", () => this._closeColumnMenu());
7878
8366
  this.removeEventListener("field-blur", this._boundFieldBlur);
7879
8367
  }
8368
+ willUpdate(changedProperties) {
8369
+ var _a;
8370
+ super.willUpdate(changedProperties);
8371
+ if (changedProperties.has("section") || changedProperties.has("calculationEnabled")) {
8372
+ this._balanceCheckTotals = this.calculationEnabled && ((_a = this.section) == null ? void 0 : _a.concepts) ? findBalanceSheetTotals(this.section.concepts) : null;
8373
+ }
8374
+ }
7880
8375
  updated(changedProperties) {
7881
8376
  var _a;
7882
8377
  super.updated(changedProperties);
@@ -8501,9 +8996,95 @@ let JupiterFormSection = class extends LitElement {
8501
8996
  this._highlightMap = /* @__PURE__ */ new Map();
8502
8997
  }
8503
8998
  }
8999
+ /**
9000
+ * JDF-095: clicking the amber "Verschil" chip highlights and scrolls to the two total rows for
9001
+ * that column, reusing the same `.highlight-total` styling the calculation-warning feature
9002
+ * already applies on field focus. Read-only — no formData/state other than the highlight map.
9003
+ */
9004
+ _handleBalanceCheckClick(columnId) {
9005
+ var _a, _b, _c;
9006
+ if (!this._balanceCheckTotals)
9007
+ return;
9008
+ const { assetsConceptId, equityConceptId } = this._balanceCheckTotals;
9009
+ const newMap = new Map(this._highlightMap);
9010
+ newMap.set(assetsConceptId, { type: "total", columnId });
9011
+ newMap.set(equityConceptId, { type: "total", columnId });
9012
+ this._highlightMap = newMap;
9013
+ const trees = Array.from(((_a = this.shadowRoot) == null ? void 0 : _a.querySelectorAll("jupiter-concept-tree")) ?? []);
9014
+ let targetField = null;
9015
+ for (const tree of trees) {
9016
+ const fields = Array.from(((_b = tree.shadowRoot) == null ? void 0 : _b.querySelectorAll("jupiter-form-field")) ?? []);
9017
+ targetField = fields.find((f2) => f2.conceptId === assetsConceptId && f2.columnId === columnId);
9018
+ if (targetField)
9019
+ break;
9020
+ }
9021
+ if (!targetField)
9022
+ return;
9023
+ targetField.scrollIntoView({ behavior: "smooth", block: "center" });
9024
+ const tableContainer = (_c = this.shadowRoot) == null ? void 0 : _c.querySelector(".table-container");
9025
+ if (tableContainer && tableContainer.scrollWidth > tableContainer.clientWidth) {
9026
+ const fieldRect = targetField.getBoundingClientRect();
9027
+ const containerRect = tableContainer.getBoundingClientRect();
9028
+ const targetScrollLeft = Math.max(
9029
+ 0,
9030
+ fieldRect.left - containerRect.left + tableContainer.scrollLeft - tableContainer.clientWidth / 2 + fieldRect.width / 2
9031
+ );
9032
+ tableContainer.scrollTo({ left: targetScrollLeft, behavior: "smooth" });
9033
+ }
9034
+ }
9035
+ /** JDF-095: formats the balance difference for display only — never affects stored/displayed cell values. */
9036
+ _formatBalanceDifferenceAmount(n3) {
9037
+ try {
9038
+ return new Intl.NumberFormat(this.locale, { maximumFractionDigits: 2 }).format(Math.abs(n3));
9039
+ } catch {
9040
+ return JupiterFormSection._formatNumber(Math.abs(n3));
9041
+ }
9042
+ }
9043
+ _renderBalanceCheckStrip() {
9044
+ if (!this.calculationEnabled || !this._balanceCheckTotals)
9045
+ return "";
9046
+ const results = computeBalanceDifference(this.formData, this.columns, this._balanceCheckTotals);
9047
+ if (results.length === 0)
9048
+ return "";
9049
+ return html`
9050
+ <div class="balance-check-strip">
9051
+ ${results.map((result) => result.balanced ? html`
9052
+ <span class="balance-check-chip balanced" title="${result.columnLabel}">
9053
+ ✓ ${I18n.t("balanceCheck.balanced", { column: result.columnLabel })}
9054
+ </span>
9055
+ ` : html`
9056
+ <button
9057
+ type="button"
9058
+ class="balance-check-chip unbalanced"
9059
+ title="${result.columnLabel}"
9060
+ @click="${() => this._handleBalanceCheckClick(result.columnId)}"
9061
+ >
9062
+ ${I18n.t("balanceCheck.difference", {
9063
+ column: result.columnLabel,
9064
+ amount: this._formatBalanceDifferenceAmount(result.difference)
9065
+ })}
9066
+ </button>
9067
+ `)}
9068
+ </div>
9069
+ `;
9070
+ }
9071
+ /**
9072
+ * Column-header typed-member storage is scoped per role/section (not just columnId): the
9073
+ * form builder assigns generic ids like "typed-duration" to any role with a single typed
9074
+ * dimension and no explicit members, so the same axis+columnId pair can occur in more than
9075
+ * one role (e.g. a "Separate" and "Consolidated" variant of the same note). Without the
9076
+ * section id, a value typed in one role's column header would leak into every other role
9077
+ * whose column happens to share that generic id (JDF-090).
9078
+ */
9079
+ _scopedColumnKey(columnId) {
9080
+ return `${this.section.id}__${columnId}`;
9081
+ }
8504
9082
  _getTypedMemberHeaderValue(columnId, axisId) {
8505
- var _a;
8506
- return ((_a = this.typedMemberData[columnId]) == null ? void 0 : _a[axisId]) || "";
9083
+ var _a, _b;
9084
+ const scopedValue = (_a = this.typedMemberData[this._scopedColumnKey(columnId)]) == null ? void 0 : _a[axisId];
9085
+ if (scopedValue !== void 0)
9086
+ return scopedValue;
9087
+ return ((_b = this.typedMemberData[columnId]) == null ? void 0 : _b[axisId]) || "";
8507
9088
  }
8508
9089
  /** True once any concept in this section has a non-empty value entered for the given column. */
8509
9090
  _columnHasAnyValue(columnId) {
@@ -8571,9 +9152,10 @@ let JupiterFormSection = class extends LitElement {
8571
9152
  _handleTypedMemberHeaderChange(event, columnId, axisId) {
8572
9153
  const target = event.target;
8573
9154
  const value = target instanceof HTMLInputElement && target.type === "checkbox" ? String(target.checked) : target.value;
8574
- console.log(`🔍 [FormSection] Typed member header change: columnId=${columnId}, axisId=${axisId}, value=${value}`);
9155
+ console.log(`🔍 [FormSection] Typed member header change: sectionId=${this.section.id}, columnId=${columnId}, axisId=${axisId}, value=${value}`);
8575
9156
  this.dispatchEvent(new CustomEvent("typed-member-change", {
8576
9157
  detail: {
9158
+ sectionId: this.section.id,
8577
9159
  columnId,
8578
9160
  axisId,
8579
9161
  value
@@ -8593,6 +9175,7 @@ let JupiterFormSection = class extends LitElement {
8593
9175
  const hasValidationError = !!((_a = this._typedMemberErrors[this._typedMemberErrorKey(columnId, typedMember.axisId)]) == null ? void 0 : _a.length);
8594
9176
  const fieldClass = `typed-member-header-field ${isRequired ? "required-field" : ""} ${hasValidationError ? "has-error" : ""}`;
8595
9177
  const requiredTitle = isRequired ? I18n.t("validation.typedMemberRequired") : "";
9178
+ const fieldTitle = [typedMember.axisLabel, requiredTitle].filter(Boolean).join(" — ");
8596
9179
  const onChange = (e2) => this._handleTypedMemberHeaderChange(e2, columnId, typedMember.axisId);
8597
9180
  const onBlur = (e2) => this._handleTypedMemberHeaderBlur(e2, columnId, typedMember);
8598
9181
  if (typeConfig.fieldType === "select" || isSelectType(typeConfig.fieldType)) {
@@ -8601,7 +9184,7 @@ let JupiterFormSection = class extends LitElement {
8601
9184
  <select
8602
9185
  class="${fieldClass}"
8603
9186
  .value="${currentValue}"
8604
- title="${requiredTitle}"
9187
+ title="${fieldTitle}"
8605
9188
  @change="${onChange}"
8606
9189
  @blur="${onBlur}"
8607
9190
  >
@@ -8620,7 +9203,7 @@ let JupiterFormSection = class extends LitElement {
8620
9203
  type="checkbox"
8621
9204
  class="${fieldClass}"
8622
9205
  .checked="${currentValue === "true"}"
8623
- title="${requiredTitle}"
9206
+ title="${fieldTitle}"
8624
9207
  @change="${onChange}"
8625
9208
  @blur="${onBlur}"
8626
9209
  />
@@ -8638,7 +9221,7 @@ let JupiterFormSection = class extends LitElement {
8638
9221
  min="${typeConfig.min !== void 0 ? typeConfig.min : ""}"
8639
9222
  max="${typeConfig.max !== void 0 ? typeConfig.max : ""}"
8640
9223
  step="${typeConfig.step !== void 0 ? typeConfig.step : ""}"
8641
- title="${requiredTitle}"
9224
+ title="${fieldTitle}"
8642
9225
  @input="${onChange}"
8643
9226
  @blur="${onBlur}"
8644
9227
  />
@@ -8681,10 +9264,12 @@ let JupiterFormSection = class extends LitElement {
8681
9264
  .defaultUnits="${this.defaultUnits}"
8682
9265
  .disabled="${this.disabled}"
8683
9266
  .locale="${this.locale}"
9267
+ .numberLocale="${this.numberLocale}"
8684
9268
  .language="${this.language}"
8685
9269
  .expandedConcepts="${this._expandedConcepts}"
8686
9270
  .mode="${this.mode}"
8687
9271
  .masterData="${this.masterData}"
9272
+ .prefilledTooltip="${this.prefilledTooltip}"
8688
9273
  .facts="${instanceConcept.facts}"
8689
9274
  .typedMemberData="${this.typedMemberData}"
8690
9275
  .showAddButton="${opts.showAdd || false}"
@@ -8744,9 +9329,10 @@ let JupiterFormSection = class extends LitElement {
8744
9329
  </div>
8745
9330
  `;
8746
9331
  }
9332
+ const { caption: columnCaption, perColumnLabel } = splitCommonMembers(this.columns);
8747
9333
  return html`
8748
9334
  ${!this.hideHeader ? html`
8749
- <div class="section-header ${!this.collapsible ? "not-collapsible" : ""}"
9335
+ <div class="section-header ${!this.collapsible ? "not-collapsible" : ""}"
8750
9336
  @click="${this._toggleExpanded}">
8751
9337
  <div>
8752
9338
  <h3 class="section-title">${this.section.title}</h3>
@@ -8763,6 +9349,8 @@ let JupiterFormSection = class extends LitElement {
8763
9349
  ` : ""}
8764
9350
 
8765
9351
  <div class="section-content ${!this._expanded ? "collapsed" : ""}">
9352
+ ${columnCaption ? html`<div class="column-caption">${columnCaption}</div>` : ""}
9353
+ ${this._renderBalanceCheckStrip()}
8766
9354
  <div class="table-container">
8767
9355
  <table class="form-table">
8768
9356
  <thead class="table-header">
@@ -8777,10 +9365,10 @@ let JupiterFormSection = class extends LitElement {
8777
9365
  @dblclick="${() => this._handleShowColumn(column2.id)}"
8778
9366
  ></th>
8779
9367
  ` : html`
8780
- <th class="header-cell ${column2.removable ? "removable" : ""}">
9368
+ <th class="header-cell ${column2.removable ? "removable" : ""}" title="${column2.title}">
8781
9369
  <div class="column-header-content">
8782
9370
  <div class="column-title">
8783
- ${column2.title}
9371
+ ${perColumnLabel[column2.id]}
8784
9372
  ${column2.description ? html`<div style="font-weight: normal; font-size: 12px; color: var(--jupiter-text-secondary, #666);">${column2.description}</div>` : ""}
8785
9373
  </div>
8786
9374
 
@@ -8968,6 +9556,53 @@ JupiterFormSection.styles = css`
8968
9556
  overflow-x: auto;
8969
9557
  }
8970
9558
 
9559
+ /* JDF-113: caption for dimension members shared by every column in the role (e.g.
9560
+ "Geconsolideerd · Commercieel"), so they don't also repeat inside every column header. */
9561
+ .column-caption {
9562
+ font-size: 13px;
9563
+ font-weight: 600;
9564
+ color: var(--jupiter-text-secondary, #666);
9565
+ padding: 4px 0 8px 0;
9566
+ }
9567
+
9568
+ /* JDF-095: live, read-only balance-difference strip (Activa = Passiva). */
9569
+ .balance-check-strip {
9570
+ display: flex;
9571
+ flex-wrap: wrap;
9572
+ gap: 8px;
9573
+ padding: 8px 0 12px 0;
9574
+ position: sticky;
9575
+ left: 0;
9576
+ }
9577
+
9578
+ .balance-check-chip {
9579
+ display: inline-flex;
9580
+ align-items: center;
9581
+ gap: 6px;
9582
+ padding: 6px 12px;
9583
+ border-radius: 16px;
9584
+ font-size: 13px;
9585
+ font-weight: 600;
9586
+ border: none;
9587
+ font-family: inherit;
9588
+ }
9589
+
9590
+ .balance-check-chip.balanced {
9591
+ background: var(--jupiter-success-light, #e8f5e9);
9592
+ color: var(--jupiter-success-color, #4caf50);
9593
+ }
9594
+
9595
+ .balance-check-chip.unbalanced {
9596
+ background: var(--jupiter-warning-light, #fff3e0);
9597
+ color: var(--jupiter-warning-color, #ff9800);
9598
+ cursor: pointer;
9599
+ }
9600
+
9601
+ .balance-check-chip.unbalanced:hover {
9602
+ background: var(--jupiter-warning-color, #ff9800);
9603
+ color: #fff;
9604
+ }
9605
+
8971
9606
  .form-table {
8972
9607
  width: 100%;
8973
9608
  table-layout: fixed;
@@ -9236,6 +9871,9 @@ __decorateClass$5([
9236
9871
  __decorateClass$5([
9237
9872
  n2({ type: String })
9238
9873
  ], JupiterFormSection.prototype, "locale", 2);
9874
+ __decorateClass$5([
9875
+ n2({ type: String })
9876
+ ], JupiterFormSection.prototype, "numberLocale", 2);
9239
9877
  __decorateClass$5([
9240
9878
  n2({ type: Boolean })
9241
9879
  ], JupiterFormSection.prototype, "isFirstSection", 2);
@@ -9257,6 +9895,9 @@ __decorateClass$5([
9257
9895
  __decorateClass$5([
9258
9896
  n2({ type: Object })
9259
9897
  ], JupiterFormSection.prototype, "masterData", 2);
9898
+ __decorateClass$5([
9899
+ n2({ type: String })
9900
+ ], JupiterFormSection.prototype, "prefilledTooltip", 2);
9260
9901
  __decorateClass$5([
9261
9902
  n2({ type: String })
9262
9903
  ], JupiterFormSection.prototype, "periodStartDate", 2);
@@ -9308,6 +9949,31 @@ __decorateClass$5([
9308
9949
  JupiterFormSection = __decorateClass$5([
9309
9950
  t$1("jupiter-form-section")
9310
9951
  ], JupiterFormSection);
9952
+ function fuzzyScore(query, text) {
9953
+ if (!query)
9954
+ return 0;
9955
+ const q = query.toLowerCase();
9956
+ const t2 = text.toLowerCase();
9957
+ const substringIndex = t2.indexOf(q);
9958
+ if (substringIndex !== -1) {
9959
+ const charBefore = t2[substringIndex - 1];
9960
+ const atWordStart = substringIndex === 0 || !charBefore || /[^a-z0-9]/.test(charBefore);
9961
+ return 1e4 - substringIndex + (atWordStart ? 500 : 0);
9962
+ }
9963
+ let qi = 0;
9964
+ let streak = 0;
9965
+ let score = 0;
9966
+ for (let ti = 0; ti < t2.length && qi < q.length; ti++) {
9967
+ if (t2[ti] === q[qi]) {
9968
+ qi++;
9969
+ streak++;
9970
+ score += streak;
9971
+ } else {
9972
+ streak = 0;
9973
+ }
9974
+ }
9975
+ return qi === q.length ? score : -1;
9976
+ }
9311
9977
  var __defProp$4 = Object.defineProperty;
9312
9978
  var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
9313
9979
  var __decorateClass$4 = (decorators, target, key, kind) => {
@@ -9319,17 +9985,101 @@ var __decorateClass$4 = (decorators, target, key, kind) => {
9319
9985
  __defProp$4(target, key, result);
9320
9986
  return result;
9321
9987
  };
9988
+ const MAX_SUGGESTIONS = 8;
9322
9989
  let JupiterAdvancedFilter = class extends LitElement {
9323
9990
  constructor() {
9324
9991
  super(...arguments);
9325
9992
  this.showFactsOnly = false;
9326
9993
  this.conceptSearchText = "";
9994
+ this.availableRoles = [];
9327
9995
  this._localConceptSearchText = "";
9996
+ this._conceptOptions = [];
9997
+ this._suggestions = [];
9998
+ this._showSuggestions = false;
9999
+ this._activeSuggestionIndex = -1;
9328
10000
  }
9329
10001
  updated(changedProperties) {
9330
10002
  if (changedProperties.has("conceptSearchText") && this.conceptSearchText !== this._localConceptSearchText) {
9331
10003
  this._localConceptSearchText = this.conceptSearchText;
9332
10004
  }
10005
+ if (changedProperties.has("availableRoles")) {
10006
+ this._conceptOptions = this._buildConceptOptions();
10007
+ }
10008
+ }
10009
+ // Unique concepts (by originalConceptId) built from presentation-derived role trees only —
10010
+ // no calculation/hypercube/datatype data is consulted, per the autosuggest requirement.
10011
+ _buildConceptOptions() {
10012
+ const seen = /* @__PURE__ */ new Set();
10013
+ const options = [];
10014
+ const walk = (concepts) => {
10015
+ var _a;
10016
+ if (!concepts)
10017
+ return;
10018
+ for (const concept of concepts) {
10019
+ const id = concept.originalConceptId || concept.id;
10020
+ if (id && !concept.abstract && !seen.has(id)) {
10021
+ seen.add(id);
10022
+ options.push({ id, name: concept.label || concept.name || id });
10023
+ }
10024
+ if ((_a = concept.children) == null ? void 0 : _a.length) {
10025
+ walk(concept.children);
10026
+ }
10027
+ }
10028
+ };
10029
+ for (const role of this.availableRoles) {
10030
+ walk(role.concepts);
10031
+ }
10032
+ return options;
10033
+ }
10034
+ _computeSuggestions(query) {
10035
+ const trimmed = query.trim();
10036
+ if (!trimmed || this._conceptOptions.length === 0) {
10037
+ return [];
10038
+ }
10039
+ const scored = [];
10040
+ for (const option of this._conceptOptions) {
10041
+ const score = Math.max(
10042
+ fuzzyScore(trimmed, option.name),
10043
+ fuzzyScore(trimmed, option.id)
10044
+ );
10045
+ if (score >= 0) {
10046
+ scored.push({ option, score });
10047
+ }
10048
+ }
10049
+ scored.sort((a2, b2) => b2.score - a2.score);
10050
+ return scored.slice(0, MAX_SUGGESTIONS).map((s2) => s2.option);
10051
+ }
10052
+ _selectSuggestion(option) {
10053
+ var _a;
10054
+ this._localConceptSearchText = option.name;
10055
+ this._showSuggestions = false;
10056
+ this._activeSuggestionIndex = -1;
10057
+ this.dispatchEvent(new CustomEvent("filter-change", {
10058
+ detail: { showFactsOnly: this.showFactsOnly, conceptSearchText: this._localConceptSearchText },
10059
+ bubbles: true,
10060
+ composed: true
10061
+ }));
10062
+ const input = (_a = this.shadowRoot) == null ? void 0 : _a.querySelector(".concept-search-input");
10063
+ input == null ? void 0 : input.focus();
10064
+ }
10065
+ _handleConceptSearchFocus() {
10066
+ if (this._localConceptSearchText.trim()) {
10067
+ this._suggestions = this._computeSuggestions(this._localConceptSearchText);
10068
+ this._showSuggestions = this._suggestions.length > 0;
10069
+ }
10070
+ }
10071
+ _handleConceptSearchBlur() {
10072
+ this._showSuggestions = false;
10073
+ this._activeSuggestionIndex = -1;
10074
+ }
10075
+ _renderHighlighted(text, query) {
10076
+ const q = query.trim().toLowerCase();
10077
+ if (!q)
10078
+ return text;
10079
+ const idx = text.toLowerCase().indexOf(q);
10080
+ if (idx === -1)
10081
+ return text;
10082
+ return html`${text.slice(0, idx)}<mark>${text.slice(idx, idx + q.length)}</mark>${text.slice(idx + q.length)}`;
9333
10083
  }
9334
10084
  _handleShowFactsOnlyChange(e2) {
9335
10085
  const checked = e2.target.checked;
@@ -9344,15 +10094,55 @@ let JupiterAdvancedFilter = class extends LitElement {
9344
10094
  }
9345
10095
  _handleConceptSearchInput(e2) {
9346
10096
  this._localConceptSearchText = this._sanitizeSearchText(e2.target.value);
10097
+ this._suggestions = this._computeSuggestions(this._localConceptSearchText);
10098
+ this._showSuggestions = this._suggestions.length > 0;
10099
+ this._activeSuggestionIndex = -1;
9347
10100
  this.dispatchEvent(new CustomEvent("filter-change", {
9348
10101
  detail: { showFactsOnly: this.showFactsOnly, conceptSearchText: this._localConceptSearchText },
9349
10102
  bubbles: true,
9350
10103
  composed: true
9351
10104
  }));
9352
10105
  }
10106
+ // The "Apply Filter" button is otherwise the only way to apply this search — pressing Enter
10107
+ // in the input did nothing, forcing a mouse trip for what looks like a normal search box.
10108
+ _handleConceptSearchKeydown(e2) {
10109
+ if (this._showSuggestions && this._suggestions.length > 0) {
10110
+ if (e2.key === "ArrowDown") {
10111
+ e2.preventDefault();
10112
+ this._activeSuggestionIndex = (this._activeSuggestionIndex + 1) % this._suggestions.length;
10113
+ return;
10114
+ }
10115
+ if (e2.key === "ArrowUp") {
10116
+ e2.preventDefault();
10117
+ this._activeSuggestionIndex = this._activeSuggestionIndex <= 0 ? this._suggestions.length - 1 : this._activeSuggestionIndex - 1;
10118
+ return;
10119
+ }
10120
+ if (e2.key === "Escape") {
10121
+ this._showSuggestions = false;
10122
+ this._activeSuggestionIndex = -1;
10123
+ return;
10124
+ }
10125
+ if (e2.key === "Enter" && this._activeSuggestionIndex >= 0) {
10126
+ e2.preventDefault();
10127
+ this._selectSuggestion(this._suggestions[this._activeSuggestionIndex]);
10128
+ return;
10129
+ }
10130
+ }
10131
+ if (e2.key !== "Enter")
10132
+ return;
10133
+ e2.preventDefault();
10134
+ this._showSuggestions = false;
10135
+ this.dispatchEvent(new CustomEvent("filter-apply", {
10136
+ bubbles: true,
10137
+ composed: true
10138
+ }));
10139
+ }
9353
10140
  _clearConceptSearch() {
9354
10141
  var _a;
9355
10142
  this._localConceptSearchText = "";
10143
+ this._suggestions = [];
10144
+ this._showSuggestions = false;
10145
+ this._activeSuggestionIndex = -1;
9356
10146
  const input = (_a = this.shadowRoot) == null ? void 0 : _a.querySelector(".concept-search-input");
9357
10147
  if (input) {
9358
10148
  input.value = "";
@@ -9389,7 +10179,14 @@ let JupiterAdvancedFilter = class extends LitElement {
9389
10179
  class="concept-search-input"
9390
10180
  placeholder="${I18n.t("filter.searchByConceptPlaceholder")}"
9391
10181
  .value="${this._localConceptSearchText}"
10182
+ autocomplete="off"
10183
+ role="combobox"
10184
+ aria-expanded="${this._showSuggestions}"
10185
+ aria-autocomplete="list"
9392
10186
  @input="${this._handleConceptSearchInput}"
10187
+ @keydown="${this._handleConceptSearchKeydown}"
10188
+ @focus="${this._handleConceptSearchFocus}"
10189
+ @blur="${this._handleConceptSearchBlur}"
9393
10190
  />
9394
10191
  ${this._localConceptSearchText ? html`
9395
10192
  <button
@@ -9398,6 +10195,22 @@ let JupiterAdvancedFilter = class extends LitElement {
9398
10195
  title="${I18n.t("filter.clearSearch")}"
9399
10196
  >×</button>
9400
10197
  ` : ""}
10198
+ ${this._showSuggestions ? html`
10199
+ <ul class="concept-suggestions" role="listbox">
10200
+ ${this._suggestions.map((option, index) => html`
10201
+ <li
10202
+ class="concept-suggestion-item ${index === this._activeSuggestionIndex ? "active" : ""}"
10203
+ role="option"
10204
+ aria-selected="${index === this._activeSuggestionIndex}"
10205
+ @mousedown="${(e2) => e2.preventDefault()}"
10206
+ @click="${() => this._selectSuggestion(option)}"
10207
+ >
10208
+ <span class="concept-suggestion-name">${this._renderHighlighted(option.name, this._localConceptSearchText)}</span>
10209
+ <span class="concept-suggestion-id">${option.id}</span>
10210
+ </li>
10211
+ `)}
10212
+ </ul>
10213
+ ` : ""}
9401
10214
  </div>
9402
10215
  </div>
9403
10216
  </div>
@@ -9504,6 +10317,56 @@ JupiterAdvancedFilter.styles = css`
9504
10317
  .concept-search-clear:hover {
9505
10318
  color: var(--primaryTextColor, var(--jupiter-text-primary, #333));
9506
10319
  }
10320
+
10321
+ .concept-suggestions {
10322
+ position: absolute;
10323
+ top: calc(100% + 4px);
10324
+ left: 0;
10325
+ right: 0;
10326
+ z-index: 20;
10327
+ margin: 0;
10328
+ padding: 4px;
10329
+ list-style: none;
10330
+ max-height: 260px;
10331
+ overflow-y: auto;
10332
+ background: var(--jupiter-background, #fff);
10333
+ border: 1px solid var(--primaryTextColor, var(--jupiter-border-color, #ddd));
10334
+ border-radius: 4px;
10335
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
10336
+ }
10337
+
10338
+ .concept-suggestion-item {
10339
+ display: flex;
10340
+ flex-direction: column;
10341
+ gap: 2px;
10342
+ padding: 6px 8px;
10343
+ border-radius: 3px;
10344
+ cursor: pointer;
10345
+ }
10346
+
10347
+ .concept-suggestion-item:hover,
10348
+ .concept-suggestion-item.active {
10349
+ background: var(--menuBgColorLighter, var(--jupiter-hover-background, #f5f5f5));
10350
+ }
10351
+
10352
+ .concept-suggestion-name {
10353
+ font-size: 13px;
10354
+ color: var(--primaryTextColor, var(--jupiter-text-primary, #333));
10355
+ }
10356
+
10357
+ .concept-suggestion-name mark {
10358
+ background: var(--jupiter-primary-color, #1976d2);
10359
+ color: white;
10360
+ padding: 0 1px;
10361
+ border-radius: 2px;
10362
+ font-weight: 500;
10363
+ }
10364
+
10365
+ .concept-suggestion-id {
10366
+ font-size: 11px;
10367
+ color: var(--primaryTextColor, var(--jupiter-text-secondary, #999));
10368
+ word-break: break-all;
10369
+ }
9507
10370
  `;
9508
10371
  __decorateClass$4([
9509
10372
  n2({ type: Boolean })
@@ -9511,9 +10374,24 @@ __decorateClass$4([
9511
10374
  __decorateClass$4([
9512
10375
  n2({ type: String })
9513
10376
  ], JupiterAdvancedFilter.prototype, "conceptSearchText", 2);
10377
+ __decorateClass$4([
10378
+ n2({ type: Array })
10379
+ ], JupiterAdvancedFilter.prototype, "availableRoles", 2);
10380
+ __decorateClass$4([
10381
+ r()
10382
+ ], JupiterAdvancedFilter.prototype, "_localConceptSearchText", 2);
10383
+ __decorateClass$4([
10384
+ r()
10385
+ ], JupiterAdvancedFilter.prototype, "_conceptOptions", 2);
10386
+ __decorateClass$4([
10387
+ r()
10388
+ ], JupiterAdvancedFilter.prototype, "_suggestions", 2);
10389
+ __decorateClass$4([
10390
+ r()
10391
+ ], JupiterAdvancedFilter.prototype, "_showSuggestions", 2);
9514
10392
  __decorateClass$4([
9515
10393
  r()
9516
- ], JupiterAdvancedFilter.prototype, "_localConceptSearchText", 2);
10394
+ ], JupiterAdvancedFilter.prototype, "_activeSuggestionIndex", 2);
9517
10395
  JupiterAdvancedFilter = __decorateClass$4([
9518
10396
  t$1("jupiter-advanced-filter")
9519
10397
  ], JupiterAdvancedFilter);
@@ -10408,9 +11286,11 @@ let JupiterFilterRolesDialog = class extends LitElement {
10408
11286
  </div>
10409
11287
  ` : this._showAdvancedFilter ? html`
10410
11288
  <jupiter-advanced-filter
11289
+ .availableRoles="${this.availableRoles}"
10411
11290
  .showFactsOnly="${this._showFactsOnly}"
10412
11291
  .conceptSearchText="${this._conceptSearchText}"
10413
11292
  @filter-change="${this._handleFilterChange}"
11293
+ @filter-apply="${this._handleAdvancedFilterApply}"
10414
11294
  ></jupiter-advanced-filter>
10415
11295
  ` : html`
10416
11296
  <p class="description">
@@ -11873,6 +12753,7 @@ let JupiterDynamicForm = class extends LitElement {
11873
12753
  this.periodEndDate = "2025-12-31";
11874
12754
  this.language = "en";
11875
12755
  this.reportingLanguage = "en";
12756
+ this.numberLocale = "";
11876
12757
  this.display = "accordion";
11877
12758
  this.mode = "inputForm";
11878
12759
  this.roleFilterAxes = [];
@@ -11904,6 +12785,8 @@ let JupiterDynamicForm = class extends LitElement {
11904
12785
  this._xbrlFormErrors = [];
11905
12786
  this._calculationWarnings = /* @__PURE__ */ new Map();
11906
12787
  this._showErrorPopup = false;
12788
+ this._showTypedDimensionWarning = false;
12789
+ this._typedDimensionWarningFacts = [];
11907
12790
  this._submitDisabled = false;
11908
12791
  this._allSections = [];
11909
12792
  this._selectedRoleIds = [];
@@ -11911,6 +12794,7 @@ let JupiterDynamicForm = class extends LitElement {
11911
12794
  this._revealRolePrompt = null;
11912
12795
  this._showFactsOnly = false;
11913
12796
  this._conceptSearchText = "";
12797
+ this._lastFocusedFieldIdentity = null;
11914
12798
  this._periodPreferences = {};
11915
12799
  this._activeSidePanelRoleId = null;
11916
12800
  this._sidePanelSearchQuery = "";
@@ -11923,6 +12807,8 @@ let JupiterDynamicForm = class extends LitElement {
11923
12807
  this._contextMenuY = 0;
11924
12808
  this._contextMenuRoleId = null;
11925
12809
  this._validationStatus = "idle";
12810
+ this._validationDotCount = 1;
12811
+ this._validationDotIntervalId = null;
11926
12812
  this._formulaValidationResults = [];
11927
12813
  this._showFormulaValidationDialog = false;
11928
12814
  this._formulaValidationService = new FormulaValidationService();
@@ -11977,6 +12863,23 @@ let JupiterDynamicForm = class extends LitElement {
11977
12863
  bubbles: true
11978
12864
  }));
11979
12865
  });
12866
+ this.addEventListener("focusin", (e2) => {
12867
+ var _a;
12868
+ const input = e2.composedPath().find(
12869
+ (el) => el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement
12870
+ );
12871
+ if (!input)
12872
+ return;
12873
+ let node = input;
12874
+ while (node && ((_a = node.tagName) == null ? void 0 : _a.toLowerCase()) !== "jupiter-form-field") {
12875
+ const root = node.getRootNode();
12876
+ node = root instanceof ShadowRoot ? root.host : null;
12877
+ }
12878
+ const fieldHost = node;
12879
+ if ((fieldHost == null ? void 0 : fieldHost.conceptId) && (fieldHost == null ? void 0 : fieldHost.columnId)) {
12880
+ this._lastFocusedFieldIdentity = { conceptId: fieldHost.conceptId, columnId: fieldHost.columnId };
12881
+ }
12882
+ });
11980
12883
  document.addEventListener("click", () => {
11981
12884
  if (this._showRoleContextMenu) {
11982
12885
  this._showRoleContextMenu = false;
@@ -12006,6 +12909,10 @@ let JupiterDynamicForm = class extends LitElement {
12006
12909
  clearInterval(this._autoSaveTimer);
12007
12910
  this._autoSaveTimer = null;
12008
12911
  }
12912
+ if (this._validationDotIntervalId !== null) {
12913
+ clearInterval(this._validationDotIntervalId);
12914
+ this._validationDotIntervalId = null;
12915
+ }
12009
12916
  this._settleRevealRolePrompt(false);
12010
12917
  }
12011
12918
  /**
@@ -12209,11 +13116,12 @@ let JupiterDynamicForm = class extends LitElement {
12209
13116
  (tm) => tm.axisId === normalizedAxisId || this._normalizeAxisId(tm.axisId) === normalizedAxisId
12210
13117
  );
12211
13118
  if (typedMember) {
12212
- if (!this._typedMemberData[matchingColumn.id]) {
12213
- this._typedMemberData[matchingColumn.id] = {};
13119
+ const columnKey = this._scopedColumnKey(section2.id, matchingColumn.id);
13120
+ if (!this._typedMemberData[columnKey]) {
13121
+ this._typedMemberData[columnKey] = {};
12214
13122
  }
12215
13123
  const axisKey = typedMember.axisId;
12216
- this._typedMemberData[matchingColumn.id][axisKey] = dim.typedMemberValue;
13124
+ this._typedMemberData[columnKey][axisKey] = dim.typedMemberValue;
12217
13125
  console.log(`✅ [DynamicForm] Set typed member value:`, {
12218
13126
  sectionId: section2.id,
12219
13127
  conceptId: concept.id,
@@ -12619,16 +13527,17 @@ let JupiterDynamicForm = class extends LitElement {
12619
13527
  }
12620
13528
  _preserveSectionData(section2) {
12621
13529
  section2.concepts.forEach((concept) => {
12622
- this._preserveConceptData(concept);
13530
+ this._preserveConceptData(concept, section2.id);
12623
13531
  });
12624
13532
  }
12625
- _preserveConceptData(concept) {
13533
+ _preserveConceptData(concept, sectionId) {
12626
13534
  if (this._formData[concept.id]) {
12627
13535
  this._preservedFormData[concept.id] = { ...this._formData[concept.id] };
12628
13536
  }
12629
13537
  concept.fields.forEach((field2) => {
12630
- if (this._typedMemberData[field2.columnId]) {
12631
- this._preservedTypedMemberData[field2.columnId] = { ...this._typedMemberData[field2.columnId] };
13538
+ const columnKey = this._scopedColumnKey(sectionId, field2.columnId);
13539
+ if (this._typedMemberData[columnKey]) {
13540
+ this._preservedTypedMemberData[columnKey] = { ...this._typedMemberData[columnKey] };
12632
13541
  }
12633
13542
  const crossRoleKey = `${concept.id}__${field2.columnId}`;
12634
13543
  if (this._typedMemberData[crossRoleKey]) {
@@ -12637,22 +13546,23 @@ let JupiterDynamicForm = class extends LitElement {
12637
13546
  });
12638
13547
  if (concept.children) {
12639
13548
  concept.children.forEach((child) => {
12640
- this._preserveConceptData(child);
13549
+ this._preserveConceptData(child, sectionId);
12641
13550
  });
12642
13551
  }
12643
13552
  }
12644
13553
  _restoreSectionData(section2) {
12645
13554
  section2.concepts.forEach((concept) => {
12646
- this._restoreConceptData(concept);
13555
+ this._restoreConceptData(concept, section2.id);
12647
13556
  });
12648
13557
  }
12649
- _restoreConceptData(concept) {
13558
+ _restoreConceptData(concept, sectionId) {
12650
13559
  if (this._preservedFormData[concept.id]) {
12651
13560
  this._formData[concept.id] = { ...this._preservedFormData[concept.id] };
12652
13561
  }
12653
13562
  concept.fields.forEach((field2) => {
12654
- if (this._preservedTypedMemberData[field2.columnId]) {
12655
- this._typedMemberData[field2.columnId] = { ...this._preservedTypedMemberData[field2.columnId] };
13563
+ const columnKey = this._scopedColumnKey(sectionId, field2.columnId);
13564
+ if (this._preservedTypedMemberData[columnKey]) {
13565
+ this._typedMemberData[columnKey] = { ...this._preservedTypedMemberData[columnKey] };
12656
13566
  }
12657
13567
  const crossRoleKey = `${concept.id}__${field2.columnId}`;
12658
13568
  if (this._preservedTypedMemberData[crossRoleKey]) {
@@ -12661,7 +13571,7 @@ let JupiterDynamicForm = class extends LitElement {
12661
13571
  });
12662
13572
  if (concept.children) {
12663
13573
  concept.children.forEach((child) => {
12664
- this._restoreConceptData(child);
13574
+ this._restoreConceptData(child, sectionId);
12665
13575
  });
12666
13576
  }
12667
13577
  }
@@ -12671,9 +13581,183 @@ let JupiterDynamicForm = class extends LitElement {
12671
13581
  get _hasActiveAdvancedFilters() {
12672
13582
  return this._showFactsOnly || this._conceptSearchText.trim().length > 0;
12673
13583
  }
13584
+ // JDF-101 item 3: the footer's role-count badge normally shows the Filter Roles selection
13585
+ // (selected/total), which the concept-search/facts-only filter never touches — exactly the
13586
+ // stale-looking "549/549" the ticket's Summary complains about. While an advanced filter is
13587
+ // active, count the roles actually rendering instead — the same visibility rule the active-filter
13588
+ // banner (`_renderActiveFilterBanner`) and the content area already use — so the badge and the
13589
+ // banner always agree on what's on screen.
13590
+ get _footerVisibleRoleCount() {
13591
+ if (!this._hasActiveAdvancedFilters || !this._currentSchema) {
13592
+ return this._selectedRoleIds.length;
13593
+ }
13594
+ return this._getSidePanelVisibleSections(this._currentSchema).length;
13595
+ }
13596
+ // JDF-101: counts the rows the concept filter leaves visible within `sectionsInView` (the
13597
+ // sections actually rendered for the current display mode — the active role alone in
13598
+ // sidePanel, every filtered role in accordion) and picks a human label for the banner.
13599
+ // Deliberately scoped to `sectionsInView` rather than the whole taxonomy: the same search text
13600
+ // can substring-match unrelated concepts in other roles (e.g. a taxonomy's technical
13601
+ // period-marker rows, JDF-106), so resolving the label globally can name a concept the user
13602
+ // never clicked. Scoping to what's on screen narrows that a lot, but the underlying search is
13603
+ // itself a broad substring match (`_getConceptSearchMatchIds`) that can still pull in dozens of
13604
+ // unrelated concepts whose name merely contains the search text as a substring (e.g. searching
13605
+ // "IntangibleAssets" also matches "IntangibleAssetsPresentationOrder", a JDF-106 technical
13606
+ // helper row) — especially in accordion mode, where every matching role renders at once. So an
13607
+ // exact local-name match (what scrollToConcept always provides, having stripped the prefix
13608
+ // itself) is preferred outright over any merely-substring match, and a non-abstract row's label
13609
+ // is preferred over an abstract group header's within each tier.
13610
+ _analyzeConceptFilterMatches(sectionsInView, matchIds) {
13611
+ const localName = (name) => (name.includes(":") ? name.split(":").pop() : name).toLowerCase();
13612
+ const searchLocal = this._conceptSearchText.trim().toLowerCase();
13613
+ let count = 0;
13614
+ let exactConcreteLabel = "";
13615
+ let exactAbstractLabel = "";
13616
+ let concreteLabel = "";
13617
+ let abstractLabel = "";
13618
+ const walk = (nodes) => {
13619
+ var _a;
13620
+ for (const node of nodes) {
13621
+ if (matchIds.has(node.originalConceptId)) {
13622
+ count++;
13623
+ const isExact = localName(node.name) === searchLocal;
13624
+ if (isExact && node.abstract && !exactAbstractLabel)
13625
+ exactAbstractLabel = node.label;
13626
+ else if (isExact && !node.abstract && !exactConcreteLabel)
13627
+ exactConcreteLabel = node.label;
13628
+ else if (node.abstract && !abstractLabel)
13629
+ abstractLabel = node.label;
13630
+ else if (!node.abstract && !concreteLabel)
13631
+ concreteLabel = node.label;
13632
+ }
13633
+ if ((_a = node.children) == null ? void 0 : _a.length)
13634
+ walk(node.children);
13635
+ }
13636
+ };
13637
+ for (const section2 of sectionsInView)
13638
+ walk(section2.concepts);
13639
+ return {
13640
+ count,
13641
+ label: exactConcreteLabel || exactAbstractLabel || concreteLabel || abstractLabel || this._conceptSearchText
13642
+ };
13643
+ }
13644
+ /**
13645
+ * JDF-101: banner shown at the top of the content area whenever a concept search or
13646
+ * "facts only" filter is narrowing what's visible, so customers don't mistake a filtered view
13647
+ * for missing data. `sectionsInView` should be whatever sections are actually rendered for the
13648
+ * current display mode (the active role alone in sidePanel; every filtered role in accordion),
13649
+ * so the row count and named concept match what the user is actually looking at. Purely a
13650
+ * read-only display — clearing it goes through the existing `_handleClearAdvancedFilters` (same
13651
+ * focus-preserving rebuild as the footer's "Clear Filters" link).
13652
+ */
13653
+ _renderActiveFilterBanner(sectionsInView) {
13654
+ if (!this._hasActiveAdvancedFilters)
13655
+ return "";
13656
+ const hasConceptSearch = this._conceptSearchText.trim().length > 0;
13657
+ const message = hasConceptSearch ? (() => {
13658
+ const matchIds = this._getConceptSearchMatchIds(this._conceptSearchText);
13659
+ const { count, label } = this._analyzeConceptFilterMatches(sectionsInView, matchIds);
13660
+ return I18n.t("filter.bannerConcept", { concept: label, count });
13661
+ })() : I18n.t("filter.bannerFactsOnly");
13662
+ return html`
13663
+ <div class="active-filter-banner">
13664
+ <span class="active-filter-banner-text">${message}</span>
13665
+ <button
13666
+ type="button"
13667
+ class="active-filter-banner-clear"
13668
+ @click="${this._handleClearAdvancedFilters}"
13669
+ >${I18n.t("filter.showAll")}</button>
13670
+ </div>
13671
+ `;
13672
+ }
12674
13673
  _handleClearAdvancedFilters() {
13674
+ const refocusTarget = this._lastFocusedFieldIdentity;
12675
13675
  this._showFactsOnly = false;
12676
13676
  this._conceptSearchText = "";
13677
+ this.updateComplete.then(async () => {
13678
+ var _a;
13679
+ const sections = Array.from(((_a = this.shadowRoot) == null ? void 0 : _a.querySelectorAll("jupiter-form-section")) ?? []);
13680
+ await Promise.all(sections.map((s2) => s2.updateComplete));
13681
+ const trees = sections.flatMap((s2) => {
13682
+ var _a2;
13683
+ return Array.from(((_a2 = s2.shadowRoot) == null ? void 0 : _a2.querySelectorAll("jupiter-concept-tree")) ?? []);
13684
+ });
13685
+ await Promise.all(trees.map((t2) => t2.updateComplete));
13686
+ this._scrollActiveRoleIntoView();
13687
+ if (refocusTarget) {
13688
+ this._restoreFieldFocus(refocusTarget);
13689
+ }
13690
+ });
13691
+ }
13692
+ // JDF-091: the locale actually used for number parsing — the explicit `numberLocale` prop when
13693
+ // a host sets one, otherwise derived from `language` so a user seeing the form in English gets
13694
+ // English number formatting rather than always Dutch (English speakers paste "100,000", not
13695
+ // "100.000"). Mirrors the existing `.locale="${config.locale || 'en-US'}"` fallback pattern.
13696
+ _effectiveNumberLocale() {
13697
+ return this.numberLocale || (this.language === "nl" ? "nl-NL" : "en-US");
13698
+ }
13699
+ // JDF-093 Part B (scoped down, per product decision 2026-09-12): a read-only "amounts are in
13700
+ // €, or € × scale" caption shown once, in the sidePanel section header — display only, derived
13701
+ // from the same `decimals` prop that already drives `DocumentIntendedRoundingLevel`
13702
+ // (_computeEffectiveMasterData), so there's no new source-of-truth question to answer and no
13703
+ // change to submission semantics. `decimals` is a positive "how many places to round off" —
13704
+ // e.g. '3' means the document rounds to the nearest 1000, i.e. amounts are being reported in
13705
+ // thousands — so the scale factor customers should read this as is 10^decimals.
13706
+ _amountScaleFactor() {
13707
+ if (this.decimals === "INF" || this.decimals === void 0 || this.decimals === null) {
13708
+ return null;
13709
+ }
13710
+ const parsed = parseFloat(this.decimals);
13711
+ if (isNaN(parsed) || parsed <= 0) {
13712
+ return null;
13713
+ }
13714
+ return Math.pow(10, parsed);
13715
+ }
13716
+ _amountScaleCaption() {
13717
+ const factor = this._amountScaleFactor();
13718
+ if (factor === null) {
13719
+ return I18n.t("section.amountScaleBase");
13720
+ }
13721
+ const formattedFactor = new Intl.NumberFormat(this._effectiveNumberLocale()).format(factor);
13722
+ return I18n.t("section.amountScaleFactor", { factor: formattedFactor });
13723
+ }
13724
+ // Only worth stating for a role that actually has a monetary amount to enter — a caption
13725
+ // reading "Amounts in €" on e.g. "Address of the legal entity" would just be noise.
13726
+ _sectionHasMonetaryConcepts(concepts) {
13727
+ return concepts.some(
13728
+ (concept) => {
13729
+ var _a;
13730
+ return ((_a = concept.type) == null ? void 0 : _a.toLowerCase().includes("monetary")) || concept.children && this._sectionHasMonetaryConcepts(concept.children);
13731
+ }
13732
+ );
13733
+ }
13734
+ // Brings the currently selected role's entry in the sidePanel left-panel list back into view
13735
+ // (it stays selected across a filter change but can scroll out of sight when the list regrows).
13736
+ // Centered rather than 'nearest': 'nearest' places the item flush against the scroll boundary,
13737
+ // which reads as "still cut off" when its row height isn't an exact multiple of the container.
13738
+ _scrollActiveRoleIntoView() {
13739
+ 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" });
13742
+ }
13743
+ // Re-locates the fact input identified by _lastFocusedFieldIdentity after a rebuild and restores
13744
+ // focus to it, mirroring the shadow-DOM traversal scrollToConcept/_handleErrorFieldClick use.
13745
+ _restoreFieldFocus(target) {
13746
+ var _a, _b, _c, _d;
13747
+ const sections = ((_a = this.shadowRoot) == null ? void 0 : _a.querySelectorAll("jupiter-form-section")) ?? [];
13748
+ for (const section2 of Array.from(sections)) {
13749
+ const trees = ((_b = section2.shadowRoot) == null ? void 0 : _b.querySelectorAll("jupiter-concept-tree")) ?? [];
13750
+ for (const tree of Array.from(trees)) {
13751
+ const fields = ((_c = tree.shadowRoot) == null ? void 0 : _c.querySelectorAll("jupiter-form-field")) ?? [];
13752
+ for (const fieldEl of Array.from(fields)) {
13753
+ if (fieldEl.conceptId === target.conceptId && fieldEl.columnId === target.columnId) {
13754
+ const input = (_d = fieldEl.shadowRoot) == null ? void 0 : _d.querySelector('input:not([type="hidden"]), select, textarea');
13755
+ input == null ? void 0 : input.focus({ preventScroll: true });
13756
+ return;
13757
+ }
13758
+ }
13759
+ }
13760
+ }
12677
13761
  }
12678
13762
  _handleFilterRolesClick() {
12679
13763
  this._showFilterDialog = true;
@@ -13166,9 +14250,21 @@ let JupiterDynamicForm = class extends LitElement {
13166
14250
  this._dirty = true;
13167
14251
  this.hasUnsavedChanges = true;
13168
14252
  }
14253
+ /**
14254
+ * Column-header typed-member storage is scoped per role/section, not just columnId: the
14255
+ * form builder assigns generic ids (e.g. "typed-duration") to any role with a single typed
14256
+ * dimension and no explicit members, so the same columnId+axisId pair can recur across
14257
+ * unrelated roles (e.g. a "Separate" and "Consolidated" variant of the same note). Without
14258
+ * the section id, a value typed into one role's column header leaks into every other role
14259
+ * whose column happens to share that generic id (JDF-090). Mirrors `_scopedColumnKey` in
14260
+ * form-section.ts.
14261
+ */
14262
+ _scopedColumnKey(sectionId, columnId) {
14263
+ return `${sectionId}__${columnId}`;
14264
+ }
13169
14265
  _handleTypedMemberChange(event) {
13170
- const { columnId, axisId, value, conceptId } = event.detail;
13171
- const storageKey = conceptId ? `${conceptId}__${columnId}` : columnId;
14266
+ const { columnId, axisId, value, conceptId, sectionId } = event.detail;
14267
+ const storageKey = conceptId ? `${conceptId}__${columnId}` : sectionId ? this._scopedColumnKey(sectionId, columnId) : columnId;
13172
14268
  console.log(`🔍 [DynamicForm] Typed member change: storageKey=${storageKey}, axisId=${axisId}, value=${value}`);
13173
14269
  const updatedTypedMemberData = { ...this._typedMemberData };
13174
14270
  if (!updatedTypedMemberData[storageKey]) {
@@ -13253,10 +14349,23 @@ let JupiterDynamicForm = class extends LitElement {
13253
14349
  return this._xbrlFormErrors.some((error2) => error2.sectionId === roleId);
13254
14350
  }
13255
14351
  async _handleErrorFieldClick(conceptId, columnId, sectionId) {
13256
- var _a, _b, _c, _d, _e, _f, _g;
13257
- console.log(`🎯 [Error Click] Attempting to focus field: ${conceptId}__${columnId} in section: ${sectionId}`);
13258
14352
  this._showErrorPopup = false;
13259
14353
  this.requestUpdate();
14354
+ await this._focusFormField(conceptId, columnId, sectionId);
14355
+ }
14356
+ async _handleTypedDimensionFactClick(conceptId, columnId, sectionId) {
14357
+ this._showTypedDimensionWarning = false;
14358
+ this.requestUpdate();
14359
+ await this._focusFormField(conceptId, columnId, sectionId);
14360
+ }
14361
+ /**
14362
+ * Reveals (hidden role/column) and focuses a specific fact's input field, with a red highlight.
14363
+ * Shared by every click-to-field affordance (validation error popup, typed-dimension warning
14364
+ * popup) so their behavior stays identical — same role/column reveal prompts, same highlight.
14365
+ */
14366
+ async _focusFormField(conceptId, columnId, sectionId) {
14367
+ var _a, _b, _c, _d, _e, _f, _g;
14368
+ console.log(`🎯 [Focus Field] Attempting to focus field: ${conceptId}__${columnId} in section: ${sectionId}`);
13260
14369
  await this.updateComplete;
13261
14370
  if (!((_a = this._currentSchema) == null ? void 0 : _a.sections.some((s2) => s2.id === sectionId))) {
13262
14371
  const section2 = this._allSections.find((s2) => s2.id === sectionId);
@@ -13273,6 +14382,7 @@ let JupiterDynamicForm = class extends LitElement {
13273
14382
  this._activeSidePanelRoleId = sectionId;
13274
14383
  this.requestUpdate();
13275
14384
  await this.updateComplete;
14385
+ this._scrollActiveRoleIntoView();
13276
14386
  await new Promise((resolve) => setTimeout(resolve, 500));
13277
14387
  }
13278
14388
  const sectionElements = (_c = this.shadowRoot) == null ? void 0 : _c.querySelectorAll("jupiter-form-section");
@@ -13622,9 +14732,9 @@ let JupiterDynamicForm = class extends LitElement {
13622
14732
  const dimensionParts = [];
13623
14733
  const dimensionKeys = [];
13624
14734
  for (const dimSelection of request.selectedDimensions) {
13625
- if (dimSelection.isTyped && dimSelection.typedValue) {
13626
- dimensionParts.push(`${dimSelection.axisLabel}: ${dimSelection.typedValue}`);
13627
- dimensionKeys.push(`${dimSelection.axisId}|${dimSelection.typedValue}`);
14735
+ if (dimSelection.isTyped) {
14736
+ dimensionParts.push(dimSelection.axisLabel);
14737
+ dimensionKeys.push(`${dimSelection.axisId}|${dimSelection.typedValue || ""}`);
13628
14738
  } else if (dimSelection.memberId && dimSelection.memberLabel) {
13629
14739
  dimensionParts.push(dimSelection.memberLabel);
13630
14740
  dimensionKeys.push(`${dimSelection.axisId}|${dimSelection.memberId}`);
@@ -13873,6 +14983,69 @@ let JupiterDynamicForm = class extends LitElement {
13873
14983
  bubbles: true
13874
14984
  }));
13875
14985
  }
14986
+ /**
14987
+ * Validate pre-flight: for every currently visible column with at least one typed dimension,
14988
+ * find columns where a typed-member value hasn't been entered yet but the user has already
14989
+ * entered fact data into that column — i.e. the column is in use but incomplete. Returns one
14990
+ * entry per such fact (a column can produce several, one per concept row with a value).
14991
+ */
14992
+ _findMissingTypedDimensionFacts() {
14993
+ var _a;
14994
+ const missingFacts = [];
14995
+ if (!this._currentSchema)
14996
+ return missingFacts;
14997
+ for (const section2 of this._currentSchema.sections) {
14998
+ for (const column2 of section2.columns || []) {
14999
+ const typedMembers = (_a = column2.dimensionData) == null ? void 0 : _a.typedMembers;
15000
+ if (!typedMembers || typedMembers.length === 0)
15001
+ continue;
15002
+ const enteredTypedValues = this._typedMemberData[this._scopedColumnKey(section2.id, column2.id)] || {};
15003
+ const hasMissingTypedValue = typedMembers.some((tm) => {
15004
+ var _a2;
15005
+ return !((_a2 = enteredTypedValues[tm.axisId]) == null ? void 0 : _a2.trim());
15006
+ });
15007
+ if (!hasMissingTypedValue)
15008
+ continue;
15009
+ this._collectEnteredFactsForColumn(section2.concepts, column2, section2, missingFacts);
15010
+ }
15011
+ }
15012
+ return missingFacts;
15013
+ }
15014
+ _collectEnteredFactsForColumn(concepts, column2, section2, out) {
15015
+ var _a, _b;
15016
+ for (const concept of concepts) {
15017
+ const value = (_a = this._formData[concept.id]) == null ? void 0 : _a[column2.id];
15018
+ if (value !== void 0 && value !== null && String(value).trim() !== "") {
15019
+ out.push({
15020
+ conceptId: concept.id,
15021
+ conceptLabel: concept.label,
15022
+ columnId: column2.id,
15023
+ columnTitle: column2.title,
15024
+ sectionId: section2.id,
15025
+ sectionTitle: section2.title,
15026
+ value
15027
+ });
15028
+ }
15029
+ if ((_b = concept.children) == null ? void 0 : _b.length) {
15030
+ this._collectEnteredFactsForColumn(concept.children, column2, section2, out);
15031
+ }
15032
+ }
15033
+ }
15034
+ /** Groups the typed-dimension warning's flat fact list by role, in first-seen order, for display. */
15035
+ _groupTypedDimensionWarningFactsByRole() {
15036
+ const groups = [];
15037
+ const groupBySectionId = /* @__PURE__ */ new Map();
15038
+ for (const fact of this._typedDimensionWarningFacts) {
15039
+ let group = groupBySectionId.get(fact.sectionId);
15040
+ if (!group) {
15041
+ group = { sectionId: fact.sectionId, sectionTitle: fact.sectionTitle, facts: [] };
15042
+ groupBySectionId.set(fact.sectionId, group);
15043
+ groups.push(group);
15044
+ }
15045
+ group.facts.push(fact);
15046
+ }
15047
+ return groups;
15048
+ }
13876
15049
  _handleSubmit() {
13877
15050
  console.log("📝 Form submission started...");
13878
15051
  this._submitted = true;
@@ -13883,6 +15056,25 @@ let JupiterDynamicForm = class extends LitElement {
13883
15056
  this._submitDisabled = false;
13884
15057
  return;
13885
15058
  }
15059
+ const missingTypedDimensionFacts = this._findMissingTypedDimensionFacts();
15060
+ if (missingTypedDimensionFacts.length > 0) {
15061
+ console.log("⚠️ [Submit] Typed dimension values missing for entered facts:", missingTypedDimensionFacts);
15062
+ this._typedDimensionWarningFacts = missingTypedDimensionFacts;
15063
+ this._showTypedDimensionWarning = true;
15064
+ this._submitDisabled = false;
15065
+ this.requestUpdate();
15066
+ return;
15067
+ }
15068
+ this._continueSubmit();
15069
+ }
15070
+ /**
15071
+ * The original Validate/submit logic (error checks + submission), extracted so "Continue to
15072
+ * validate" on the typed-dimension warning popup can re-enter it directly without re-running
15073
+ * (and re-triggering) the typed-dimension pre-flight in _handleSubmit() above.
15074
+ */
15075
+ _continueSubmit() {
15076
+ this._submitted = true;
15077
+ this._submitDisabled = true;
13886
15078
  console.log(`🔵 [Submit] Checking for errors...`);
13887
15079
  console.log(`🔵 [Submit] _xbrlFormErrors.length: ${this._xbrlFormErrors.length}`);
13888
15080
  console.log(`🔵 [Submit] _xbrlFormErrors:`, this._xbrlFormErrors);
@@ -14938,12 +16130,12 @@ let JupiterDynamicForm = class extends LitElement {
14938
16130
  const sectionTitle = (section2 == null ? void 0 : section2.title) || "Unknown";
14939
16131
  concepts.forEach((concept) => {
14940
16132
  if (sectionTitle === targetRole) {
14941
- console.warn(`[DUPLICATE DEBUG] Processing concept ${concept.originalConceptId || concept.id} in target role`);
16133
+ console.log(`[DUPLICATE DEBUG] Processing concept ${concept.originalConceptId || concept.id} in target role`);
14942
16134
  if (concept.fields) {
14943
- console.warn(`[DUPLICATE DEBUG] Concept has ${concept.fields.length} fields`);
16135
+ console.log(`[DUPLICATE DEBUG] Concept has ${concept.fields.length} fields`);
14944
16136
  concept.fields.forEach((field2, index) => {
14945
16137
  var _a;
14946
- console.warn(`[DUPLICATE DEBUG] Field ${index}: columnId=${field2.columnId}, current value=${(_a = this._formData[concept.id]) == null ? void 0 : _a[field2.columnId]}`);
16138
+ console.log(`[DUPLICATE DEBUG] Field ${index}: columnId=${field2.columnId}, current value=${(_a = this._formData[concept.id]) == null ? void 0 : _a[field2.columnId]}`);
14947
16139
  });
14948
16140
  }
14949
16141
  }
@@ -14967,7 +16159,7 @@ let JupiterDynamicForm = class extends LitElement {
14967
16159
  const masterValue = (_a = this._effectiveMasterData) == null ? void 0 : _a[baseConceptId];
14968
16160
  if (masterValue !== void 0 && masterValue !== null && masterValue !== "") {
14969
16161
  fieldValue = masterValue;
14970
- console.warn(` 📦 [Submission] Using masterData for: ${baseConceptId} [${field2.columnId}] = ${JSON.stringify(fieldValue)}`);
16162
+ console.log(` 📦 [Submission] Using masterData for: ${baseConceptId} [${field2.columnId}] = ${JSON.stringify(fieldValue)}`);
14971
16163
  }
14972
16164
  }
14973
16165
  if (fieldValue !== void 0 && fieldValue !== null && fieldValue !== "") {
@@ -15014,17 +16206,20 @@ let JupiterDynamicForm = class extends LitElement {
15014
16206
  } else {
15015
16207
  console.log(`🔍 [DynamicForm] No dimension data found for field column ${field2.columnId}. Column type: ${column2 == null ? void 0 : column2.type}, has dimensionData: ${!!(column2 == null ? void 0 : column2.dimensionData)}`);
15016
16208
  }
15017
- if (this._typedMemberData[field2.columnId]) {
16209
+ const headerColumnKey = section2 ? this._scopedColumnKey(section2.id, field2.columnId) : field2.columnId;
16210
+ const scopedTypedMemberData = this._typedMemberData[headerColumnKey];
16211
+ const legacyTypedMemberData = this._typedMemberData[field2.columnId];
16212
+ if (scopedTypedMemberData || legacyTypedMemberData) {
15018
16213
  const applicableTypedDimensions = this._getApplicableTypedDimensions(concept, section2);
15019
16214
  console.log(`🔍 [DynamicForm] Concept ${concept.id} applicable typed dimensions:`, applicableTypedDimensions);
15020
16215
  if (applicableTypedDimensions.length > 0) {
15021
16216
  const filteredTypedMembers = {};
15022
- const allTypedMemberData = this._typedMemberData[field2.columnId];
15023
16217
  applicableTypedDimensions.forEach((dimensionId) => {
15024
- if (allTypedMemberData[dimensionId]) {
16218
+ const value = (scopedTypedMemberData == null ? void 0 : scopedTypedMemberData[dimensionId]) ?? (legacyTypedMemberData == null ? void 0 : legacyTypedMemberData[dimensionId]);
16219
+ if (value) {
15025
16220
  const memberName = this._getTypedMemberNameForAxis(dimensionId, section2);
15026
16221
  filteredTypedMembers[dimensionId] = {
15027
- value: allTypedMemberData[dimensionId],
16222
+ value,
15028
16223
  memberName: memberName || "Unknown"
15029
16224
  };
15030
16225
  }
@@ -15070,7 +16265,7 @@ let JupiterDynamicForm = class extends LitElement {
15070
16265
  }
15071
16266
  }
15072
16267
  if (sectionTitle === targetRole) {
15073
- console.warn(`[DUPLICATE DEBUG] Final submission entry:`, submissionEntry);
16268
+ console.log(`[DUPLICATE DEBUG] Final submission entry:`, submissionEntry);
15074
16269
  }
15075
16270
  submissionData.push(submissionEntry);
15076
16271
  }
@@ -15261,45 +16456,45 @@ let JupiterDynamicForm = class extends LitElement {
15261
16456
  console.warn(`⚠️ Section not found for roleId: ${roleId}`);
15262
16457
  return;
15263
16458
  }
15264
- console.warn(`
16459
+ console.log(`
15265
16460
  ========================================`);
15266
- console.warn(`🔍 ANALYZING ROLE: "${section2.title}"`);
15267
- console.warn(`========================================`);
16461
+ console.log(`🔍 ANALYZING ROLE: "${section2.title}"`);
16462
+ console.log(`========================================`);
15268
16463
  if (this.masterData) {
15269
- console.warn(`📦 masterData keys:`, Object.keys(this.masterData));
16464
+ console.log(`📦 masterData keys:`, Object.keys(this.masterData));
15270
16465
  } else {
15271
- console.warn(`📦 masterData: NOT PROVIDED`);
16466
+ console.log(`📦 masterData: NOT PROVIDED`);
15272
16467
  }
15273
16468
  const allFields = [];
15274
16469
  this._collectAllFields(section2.concepts || [], new Set((section2.columns || []).map((col) => col.id)), section2.columns || [], allFields);
15275
16470
  const hasErrors = this._roleHasErrors(roleId);
15276
16471
  const emptyCount = allFields.filter((f2) => f2.isEmpty).length;
15277
16472
  const filledCount = allFields.filter((f2) => !f2.isEmpty).length;
15278
- console.warn(`
16473
+ console.log(`
15279
16474
  📈 SUMMARY:`);
15280
- console.warn(` Total Fields: ${allFields.length}`);
15281
- console.warn(` Filled: ${filledCount}`);
15282
- console.warn(` Empty: ${emptyCount}`);
15283
- console.warn(` Has Errors: ${hasErrors}`);
16475
+ console.log(` Total Fields: ${allFields.length}`);
16476
+ console.log(` Filled: ${filledCount}`);
16477
+ console.log(` Empty: ${emptyCount}`);
16478
+ console.log(` Has Errors: ${hasErrors}`);
15284
16479
  let result;
15285
16480
  if (hasErrors) {
15286
16481
  result = "INVALID";
15287
- console.warn(`
16482
+ console.log(`
15288
16483
  🔴 RESULT: INVALID (contains validation errors)`);
15289
16484
  } else if (filledCount === 0) {
15290
16485
  result = "UNTOUCHED";
15291
- console.warn(`
16486
+ console.log(`
15292
16487
  ⬜ RESULT: UNTOUCHED (no data entered)`);
15293
16488
  } else if (emptyCount > 0) {
15294
16489
  result = "INCOMPLETE";
15295
- console.warn(`
16490
+ console.log(`
15296
16491
  🟠 RESULT: INCOMPLETE (${emptyCount} field(s) blank)`);
15297
16492
  } else {
15298
16493
  result = "COMPLETE";
15299
- console.warn(`
16494
+ console.log(`
15300
16495
  🟢 RESULT: COMPLETE (all fields filled)`);
15301
16496
  }
15302
- console.warn(`========================================
16497
+ console.log(`========================================
15303
16498
  `);
15304
16499
  if (result === "INVALID")
15305
16500
  ;
@@ -15335,7 +16530,7 @@ let JupiterDynamicForm = class extends LitElement {
15335
16530
  const masterValue = (_c = this._effectiveMasterData) == null ? void 0 : _c[fieldConceptId];
15336
16531
  if ((value === null || value === void 0 || typeof value === "string" && value.trim() === "") && masterValue !== void 0) {
15337
16532
  value = masterValue;
15338
- console.warn(` 📦 Using masterData value for: ${fieldConceptId} [${field2.columnId}] = ${JSON.stringify(value)}`);
16533
+ console.log(` 📦 Using masterData value for: ${fieldConceptId} [${field2.columnId}] = ${JSON.stringify(value)}`);
15339
16534
  }
15340
16535
  let isEmpty = value === null || value === void 0 || typeof value === "string" && value.trim() === "";
15341
16536
  if (isEmpty && concept.facts && concept.facts.length > 0) {
@@ -15380,6 +16575,7 @@ let JupiterDynamicForm = class extends LitElement {
15380
16575
  searchInput.value = "";
15381
16576
  searchInput.focus();
15382
16577
  }
16578
+ this.updateComplete.then(() => this._scrollActiveRoleIntoView());
15383
16579
  }
15384
16580
  _toggleSidePanelCollapse() {
15385
16581
  this._sidePanelCollapsed = !this._sidePanelCollapsed;
@@ -15835,8 +17031,20 @@ let JupiterDynamicForm = class extends LitElement {
15835
17031
  return null;
15836
17032
  }
15837
17033
  _renderAccordionLayout(schema, config, showValidationSummary, errorCount) {
17034
+ const conceptMatchIds = this._getConceptSearchMatchIds(this._conceptSearchText);
17035
+ const hasConceptSearch = this._conceptSearchText.trim().length > 0;
17036
+ let filteredSections = schema.sections;
17037
+ if (this.mode === "readonly" || this._showFactsOnly) {
17038
+ filteredSections = filteredSections.filter((s2) => !this._isSectionBlank(s2));
17039
+ }
17040
+ if (hasConceptSearch) {
17041
+ filteredSections = filteredSections.filter((s2) => this._isSectionConceptSearchMatch(s2, conceptMatchIds));
17042
+ }
15838
17043
  return html`
15839
17044
  <div class="form-sections">
17045
+ <!-- JDF-101: names the active concept/facts-only filter so a narrowed view isn't mistaken for missing data -->
17046
+ ${this.mode !== "admin" ? this._renderActiveFilterBanner(filteredSections) : ""}
17047
+
15840
17048
  <!-- Validation Summary -->
15841
17049
  ${showValidationSummary ? html`
15842
17050
  <div class="validation-summary ${errorCount === 0 ? "warnings-only" : ""}">
@@ -15861,15 +17069,7 @@ let JupiterDynamicForm = class extends LitElement {
15861
17069
  <p>${I18n.t("filter.roles")}: ${this._allSections.length}</p>
15862
17070
  </div>
15863
17071
  ` : (() => {
15864
- const conceptMatchIds = this._getConceptSearchMatchIds(this._conceptSearchText);
15865
- const hasConceptSearch = this._conceptSearchText.trim().length > 0;
15866
- let sections = schema.sections;
15867
- if (this.mode === "readonly" || this._showFactsOnly) {
15868
- sections = sections.filter((s2) => !this._isSectionBlank(s2));
15869
- }
15870
- if (hasConceptSearch) {
15871
- sections = sections.filter((s2) => this._isSectionConceptSearchMatch(s2, conceptMatchIds));
15872
- }
17072
+ const sections = filteredSections;
15873
17073
  return sections.map((section2, index) => {
15874
17074
  var _a;
15875
17075
  return html`
@@ -15889,6 +17089,7 @@ let JupiterDynamicForm = class extends LitElement {
15889
17089
  .disabled="${this.disabled || this.readonly}"
15890
17090
  .collapsible="${config.collapsibleSections !== false}"
15891
17091
  .locale="${config.locale || "en-US"}"
17092
+ .numberLocale="${this._effectiveNumberLocale()}"
15892
17093
  .language="${this.language}"
15893
17094
  .isFirstSection="${index === 0}"
15894
17095
  .availableDimensions="${this._getAvailableDimensionsForSection(section2.id)}"
@@ -15896,6 +17097,7 @@ let JupiterDynamicForm = class extends LitElement {
15896
17097
  .showFactsOnly="${this._showFactsOnly}"
15897
17098
  .conceptMatchIds="${hasConceptSearch ? conceptMatchIds : void 0}"
15898
17099
  .masterData="${this._effectiveMasterData}"
17100
+ .prefilledTooltip="${this.prefilledTooltip}"
15899
17101
  .periodStartDate="${this.periodStartDate}"
15900
17102
  .periodEndDate="${this.periodEndDate}"
15901
17103
  .calculationEnabled="${this.calculationEnabled}"
@@ -15918,19 +17120,53 @@ let JupiterDynamicForm = class extends LitElement {
15918
17120
  </div>
15919
17121
  `;
15920
17122
  }
15921
- _renderSidePanelLayout(schema, config, showValidationSummary, errorCount) {
15922
- var _a;
17123
+ // Sections visible in the side panel before the search box's own filter is applied — shared
17124
+ // by the render path and the Previous/Next navigation so both agree on "what's in the list".
17125
+ // Also reused by the footer's role-count badge (`_footerVisibleRoleCount`, JDF-101 item 3),
17126
+ // since the readonly/facts-only/concept-search visibility rule computed here is the same one
17127
+ // `_renderAccordionLayout` recomputes inline for its own copy of the active-filter banner.
17128
+ _getSidePanelVisibleSections(schema) {
15923
17129
  const conceptMatchIds = this._getConceptSearchMatchIds(this._conceptSearchText);
15924
17130
  const hasConceptSearch = this._conceptSearchText.trim().length > 0;
15925
17131
  let visibleSections = this.mode === "readonly" || this._showFactsOnly ? schema.sections.filter((s2) => !this._isSectionBlank(s2)) : schema.sections;
15926
17132
  if (hasConceptSearch) {
15927
17133
  visibleSections = visibleSections.filter((s2) => this._isSectionConceptSearchMatch(s2, conceptMatchIds));
15928
17134
  }
17135
+ return visibleSections;
17136
+ }
17137
+ // Moves the active side-panel role to the previous/next entry in the currently visible list
17138
+ // (JDF-099), scrolls the content back to the top, and brings the new role into view on the left.
17139
+ _handleSidePanelPrevNext(direction) {
17140
+ const schema = this._currentSchema;
17141
+ if (!schema)
17142
+ return;
17143
+ const filteredSections = this._filterSidePanelSections(this._getSidePanelVisibleSections(schema));
17144
+ const activeIndex = filteredSections.findIndex((s2) => s2.id === this._activeSidePanelRoleId);
17145
+ if (activeIndex === -1)
17146
+ return;
17147
+ const targetSection = filteredSections[direction === "prev" ? activeIndex - 1 : activeIndex + 1];
17148
+ if (!targetSection)
17149
+ return;
17150
+ this._handleSidePanelRoleClick(targetSection.id);
17151
+ this.updateComplete.then(() => {
17152
+ var _a;
17153
+ const contentEl = (_a = this.shadowRoot) == null ? void 0 : _a.querySelector(".side-panel-content");
17154
+ contentEl == null ? void 0 : contentEl.scrollTo({ top: 0, behavior: "smooth" });
17155
+ this._scrollActiveRoleIntoView();
17156
+ });
17157
+ }
17158
+ _renderSidePanelLayout(schema, config, showValidationSummary, errorCount) {
17159
+ var _a;
17160
+ const conceptMatchIds = this._getConceptSearchMatchIds(this._conceptSearchText);
17161
+ const hasConceptSearch = this._conceptSearchText.trim().length > 0;
17162
+ const visibleSections = this._getSidePanelVisibleSections(schema);
15929
17163
  const filteredSections = this._filterSidePanelSections(visibleSections);
15930
- if (!this._activeSidePanelRoleId || !filteredSections.find((s2) => s2.id === this._activeSidePanelRoleId)) {
17164
+ if (!this._activeSidePanelRoleId || !visibleSections.find((s2) => s2.id === this._activeSidePanelRoleId)) {
15931
17165
  this._activeSidePanelRoleId = filteredSections.length > 0 ? filteredSections[0].id : null;
15932
17166
  }
15933
17167
  const activeSection = visibleSections.find((s2) => s2.id === this._activeSidePanelRoleId);
17168
+ const activeSectionIndex = activeSection ? filteredSections.findIndex((s2) => s2.id === activeSection.id) : -1;
17169
+ const nextSection = activeSectionIndex >= 0 ? filteredSections[activeSectionIndex + 1] : void 0;
15934
17170
  return html`
15935
17171
  <div class="side-panel-layout">
15936
17172
  <!-- Left: Roles List (filtered) -->
@@ -15966,7 +17202,7 @@ let JupiterDynamicForm = class extends LitElement {
15966
17202
  </svg>
15967
17203
  `}
15968
17204
  </div>
15969
-
17205
+
15970
17206
  <!-- Search Results Info -->
15971
17207
  ${this._sidePanelSearchQuery.trim() && filteredSections.length !== visibleSections.length ? html`
15972
17208
  <div class="side-panel-search-results-info">
@@ -16003,6 +17239,9 @@ let JupiterDynamicForm = class extends LitElement {
16003
17239
  <!-- Right: Active Section Content -->
16004
17240
  <div class="side-panel-content">
16005
17241
  <div class="form-sections">
17242
+ <!-- JDF-101: names the active concept/facts-only filter so a narrowed view isn't mistaken for missing data -->
17243
+ ${this.mode !== "admin" ? this._renderActiveFilterBanner(activeSection ? [activeSection] : []) : ""}
17244
+
16006
17245
  <!-- Validation Summary -->
16007
17246
  ${showValidationSummary ? html`
16008
17247
  <div class="validation-summary ${errorCount === 0 ? "warnings-only" : ""}">
@@ -16021,6 +17260,25 @@ let JupiterDynamicForm = class extends LitElement {
16021
17260
  ${this._renderAdminModeContent(activeSection)}
16022
17261
  </div>
16023
17262
  ` : activeSection ? html`
17263
+ <div
17264
+ class="side-panel-content-header"
17265
+ role="button"
17266
+ tabindex="0"
17267
+ title="${I18n.t("section.locateInList")}"
17268
+ @click="${() => this._scrollActiveRoleIntoView()}"
17269
+ @keydown="${(e2) => {
17270
+ if (e2.key === "Enter" || e2.key === " ") {
17271
+ e2.preventDefault();
17272
+ this._scrollActiveRoleIntoView();
17273
+ }
17274
+ }}"
17275
+ >
17276
+ <div class="side-panel-content-position">${I18n.t("section.position", { current: activeSectionIndex + 1, total: filteredSections.length })}</div>
17277
+ <h2 class="side-panel-content-title">${activeSection.title}</h2>
17278
+ ${this._sectionHasMonetaryConcepts(activeSection.concepts) ? html`
17279
+ <div class="side-panel-content-amount-scale">${this._amountScaleCaption()}</div>
17280
+ ` : ""}
17281
+ </div>
16024
17282
  <jupiter-form-section
16025
17283
  section-id="${activeSection.id}"
16026
17284
  .section="${activeSection}"
@@ -16038,6 +17296,7 @@ let JupiterDynamicForm = class extends LitElement {
16038
17296
  .collapsible="${false}"
16039
17297
  .hideHeader="${true}"
16040
17298
  .locale="${config.locale || "en-US"}"
17299
+ .numberLocale="${this._effectiveNumberLocale()}"
16041
17300
  .language="${this.language}"
16042
17301
  .isFirstSection="${true}"
16043
17302
  .availableDimensions="${this._getAvailableDimensionsForSection(activeSection.id)}"
@@ -16045,6 +17304,7 @@ let JupiterDynamicForm = class extends LitElement {
16045
17304
  .showFactsOnly="${this._showFactsOnly}"
16046
17305
  .conceptMatchIds="${hasConceptSearch ? conceptMatchIds : void 0}"
16047
17306
  .masterData="${this._effectiveMasterData}"
17307
+ .prefilledTooltip="${this.prefilledTooltip}"
16048
17308
  .periodStartDate="${this.periodStartDate}"
16049
17309
  .periodEndDate="${this.periodEndDate}"
16050
17310
  .calculationEnabled="${this.calculationEnabled}"
@@ -16069,6 +17329,22 @@ let JupiterDynamicForm = class extends LitElement {
16069
17329
  >
16070
17330
  <label for="role-complete-${activeSection.id}">${I18n.t("form.markAsComplete")}</label>
16071
17331
  </div>
17332
+ <div class="side-panel-nav-row">
17333
+ ${activeSectionIndex > 0 ? html`
17334
+ <button
17335
+ type="button"
17336
+ class="side-panel-nav-btn side-panel-nav-prev"
17337
+ @click="${() => this._handleSidePanelPrevNext("prev")}"
17338
+ >${I18n.t("section.previous")}</button>
17339
+ ` : html`<span></span>`}
17340
+ ${nextSection ? html`
17341
+ <button
17342
+ type="button"
17343
+ class="side-panel-nav-btn side-panel-nav-next"
17344
+ @click="${() => this._handleSidePanelPrevNext("next")}"
17345
+ >${I18n.t("section.next", { title: nextSection.title })}</button>
17346
+ ` : ""}
17347
+ </div>
16072
17348
  ` : html`
16073
17349
  <div class="no-roles-message">
16074
17350
  <h3>${I18n.t("form.noRoleSelected")}</h3>
@@ -16107,8 +17383,23 @@ let JupiterDynamicForm = class extends LitElement {
16107
17383
  } else {
16108
17384
  this._validationStatus = status;
16109
17385
  }
17386
+ this._syncValidationDotAnimation();
16110
17387
  this.requestUpdate();
16111
17388
  }
17389
+ /** Starts/stops the Validate button's dot-cycling animation to match _validationStatus. */
17390
+ _syncValidationDotAnimation() {
17391
+ if (this._validationStatus === "inProgress") {
17392
+ if (this._validationDotIntervalId === null) {
17393
+ this._validationDotCount = 1;
17394
+ this._validationDotIntervalId = setInterval(() => {
17395
+ this._validationDotCount = this._validationDotCount % 3 + 1;
17396
+ }, 500);
17397
+ }
17398
+ } else if (this._validationDotIntervalId !== null) {
17399
+ clearInterval(this._validationDotIntervalId);
17400
+ this._validationDotIntervalId = null;
17401
+ }
17402
+ }
16112
17403
  reset() {
16113
17404
  this._handleReset();
16114
17405
  }
@@ -16348,7 +17639,7 @@ let JupiterDynamicForm = class extends LitElement {
16348
17639
  console.log(`[scrollToConcept] Section "${section2.id}" has concept "${found.id}" (no dims, taking first)`);
16349
17640
  return { concept: found, strong: true, inPeriod: inPeriod(cols.map((c2) => c2.id)) };
16350
17641
  }
16351
- async scrollToConcept(conceptName, dimensions, match, columnId, exactConceptId, role) {
17642
+ async scrollToConcept(conceptName, dimensions, match, columnId, exactConceptId, role, options) {
16352
17643
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
16353
17644
  console.log(`[scrollToConcept] ▶ START conceptName=${conceptName} value=${match == null ? void 0 : match.value} period=${JSON.stringify(match == null ? void 0 : match.period)} dims=${JSON.stringify(dimensions)} columnId=${columnId} exactConceptId=${exactConceptId} role=${role}`);
16354
17645
  const hasValueMatch = (match == null ? void 0 : match.value) !== void 0 && (match == null ? void 0 : match.value) !== null;
@@ -16424,9 +17715,14 @@ let JupiterDynamicForm = class extends LitElement {
16424
17715
  }
16425
17716
  const periodPinned = !!period && !!targetColumnId && this._periodMatches(this._cellPeriod(targetSection, targetConcept, targetColumnId), period);
16426
17717
  console.log(`[scrollToConcept] targetColumnId=${targetColumnId} hasValueMatch=${hasValueMatch} targetValue=${targetValue} periodPinned=${periodPinned}`);
16427
- this._conceptSearchText = conceptName.includes(":") ? conceptName.split(":").pop() : conceptName;
17718
+ if ((options == null ? void 0 : options.filterToConcept) !== false) {
17719
+ this._conceptSearchText = conceptName.includes(":") ? conceptName.split(":").pop() : conceptName;
17720
+ }
16428
17721
  this.requestUpdate();
16429
17722
  await this.updateComplete;
17723
+ if (this.display === "sidePanel") {
17724
+ this._scrollActiveRoleIntoView();
17725
+ }
16430
17726
  await new Promise((resolve) => setTimeout(resolve, 300));
16431
17727
  const sectionElements = (_d = this.shadowRoot) == null ? void 0 : _d.querySelectorAll("jupiter-form-section");
16432
17728
  console.log(`[scrollToConcept] DOM: found ${(sectionElements == null ? void 0 : sectionElements.length) ?? 0} jupiter-form-section elements`);
@@ -16637,7 +17933,14 @@ let JupiterDynamicForm = class extends LitElement {
16637
17933
  <path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
16638
17934
  </svg>
16639
17935
  ${this._selectedRoleIds.length === 0 ? I18n.t("filter.selectRoles") : I18n.t("filter.filterRoles")}
16640
- <span class="roles-count">${this._selectedRoleIds.length}/${this._allSections.length}</span>
17936
+ <span class="roles-count">
17937
+ ${this._hasActiveAdvancedFilters ? html`
17938
+ <svg class="roles-count-filter-icon" viewBox="0 0 24 24">
17939
+ <path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
17940
+ </svg>
17941
+ ` : ""}
17942
+ ${this._footerVisibleRoleCount}/${this._allSections.length}
17943
+ </span>
16641
17944
  </button>
16642
17945
  ${this._hasActiveAdvancedFilters ? html`
16643
17946
  <button
@@ -16662,16 +17965,6 @@ let JupiterDynamicForm = class extends LitElement {
16662
17965
 
16663
17966
 
16664
17967
  <!-- Validation state buttons -->
16665
- ${this._validationStatus === "inProgress" ? html`
16666
- <button
16667
- class="btn-preview"
16668
- @click="${() => this.dispatchEvent(new CustomEvent("showPreview", { bubbles: true, composed: true }))}"
16669
- ?disabled="${this.disabled || this.readonly}"
16670
- >
16671
- ${I18n.t("form.download")}
16672
- </button>
16673
- ` : ""}
16674
-
16675
17968
  ${this._validationStatus === "complete" || this.showLastValidationResultBtn ? html`
16676
17969
  <button
16677
17970
  class="btn-secondary btn-last-results"
@@ -16693,11 +17986,11 @@ let JupiterDynamicForm = class extends LitElement {
16693
17986
  ` : ""}
16694
17987
 
16695
17988
  <button
16696
- class="btn-primary"
16697
- @click="${this._handleSubmit}"
16698
- ?disabled="${this.disabled || this.readonly || this._submitDisabled || this._validationStatus === "inProgress"}"
17989
+ class="btn-primary ${this._validationStatus === "inProgress" ? "btn-primary--validating" : ""}"
17990
+ @click="${this._validationStatus === "inProgress" ? () => this.dispatchEvent(new CustomEvent("resume-validation-progress", { bubbles: true, composed: true })) : this._handleSubmit}"
17991
+ ?disabled="${this.disabled || this.readonly || this._submitDisabled}"
16699
17992
  >
16700
- ${this.submitButtonLabel || (this.mode === "admin" ? I18n.t("form.save") : I18n.t("form.submit"))}
17993
+ ${this._validationStatus === "inProgress" ? `${I18n.t("form.validationInProgress")}${".".repeat(this._validationDotCount)}` : this.submitButtonLabel || (this.mode === "admin" ? I18n.t("form.save") : I18n.t("form.submit"))}
16701
17994
  </button>
16702
17995
 
16703
17996
  </div>
@@ -16799,7 +18092,61 @@ let JupiterDynamicForm = class extends LitElement {
16799
18092
  </div>
16800
18093
  </div>
16801
18094
  ` : ""}
16802
-
18095
+
18096
+ <!-- Typed Dimension Warning Popup -->
18097
+ ${this._showTypedDimensionWarning ? html`
18098
+ <div class="error-popup-overlay" @click="${() => {
18099
+ this._showTypedDimensionWarning = false;
18100
+ this.requestUpdate();
18101
+ }}">
18102
+ <div class="error-popup" @click="${(e2) => e2.stopPropagation()}">
18103
+ <div class="error-popup-header">
18104
+ <h3>⚠️ ${I18n.t("typedDimensionWarning.title")}</h3>
18105
+ <button class="error-popup-close" @click="${() => {
18106
+ this._showTypedDimensionWarning = false;
18107
+ this.requestUpdate();
18108
+ }}">×</button>
18109
+ </div>
18110
+ <div class="error-popup-content">
18111
+ <p>${I18n.t("typedDimensionWarning.message")}</p>
18112
+ <ul class="error-list">
18113
+ ${this._groupTypedDimensionWarningFactsByRole().map((group) => html`
18114
+ <li>
18115
+ <strong>${I18n.t("typedDimensionWarning.role")}</strong> ${group.sectionTitle}
18116
+ <ul>
18117
+ ${group.facts.map((fact) => html`
18118
+ <li>
18119
+ <strong>${I18n.t("typedDimensionWarning.field")}</strong>
18120
+ <a
18121
+ href="javascript:void(0)"
18122
+ class="error-field-link"
18123
+ @click="${() => this._handleTypedDimensionFactClick(fact.conceptId, fact.columnId, fact.sectionId)}"
18124
+ title="${I18n.t("typedDimensionWarning.clickToFocus")}"
18125
+ >
18126
+ ${fact.conceptLabel} (${fact.columnTitle})
18127
+ </a>
18128
+ — <strong>${I18n.t("typedDimensionWarning.value")}</strong> ${fact.value}
18129
+ </li>
18130
+ `)}
18131
+ </ul>
18132
+ </li>
18133
+ `)}
18134
+ </ul>
18135
+ </div>
18136
+ <div class="error-popup-footer">
18137
+ <button class="btn-secondary" @click="${() => {
18138
+ this._showTypedDimensionWarning = false;
18139
+ this.requestUpdate();
18140
+ }}">${I18n.t("typedDimensionWarning.cancel")}</button>
18141
+ <button class="btn-primary" @click="${() => {
18142
+ this._showTypedDimensionWarning = false;
18143
+ this._continueSubmit();
18144
+ }}">${I18n.t("typedDimensionWarning.continue")}</button>
18145
+ </div>
18146
+ </div>
18147
+ </div>
18148
+ ` : ""}
18149
+
16803
18150
  <!-- Role Context Menu -->
16804
18151
  ${this._showRoleContextMenu ? html`
16805
18152
  <div
@@ -16918,7 +18265,45 @@ JupiterDynamicForm.styles = css`
16918
18265
  color: var(--jupiter-warning-color, #ff9800);
16919
18266
  }
16920
18267
 
16921
- .form-actions button {
18268
+ /* JDF-101: banner naming an active concept/facts-only filter — informational (blue), distinct
18269
+ from the error/warning red-amber palette above, since a filter isn't a problem to fix. */
18270
+ .active-filter-banner {
18271
+ display: flex;
18272
+ align-items: center;
18273
+ justify-content: space-between;
18274
+ gap: 12px;
18275
+ margin-bottom: 16px;
18276
+ padding: 10px 16px;
18277
+ border-radius: 4px;
18278
+ background: var(--jupiter-info-background, #e3f2fd);
18279
+ border: 1px solid var(--jupiter-primary-color, #1976d2);
18280
+ color: var(--jupiter-primary-color, #1976d2);
18281
+ font-size: 14px;
18282
+ }
18283
+
18284
+ .active-filter-banner-text {
18285
+ font-weight: 500;
18286
+ }
18287
+
18288
+ .active-filter-banner-clear {
18289
+ flex-shrink: 0;
18290
+ background: none;
18291
+ border: 1px solid var(--jupiter-primary-color, #1976d2);
18292
+ border-radius: 4px;
18293
+ padding: 6px 12px;
18294
+ font-size: 13px;
18295
+ font-weight: 600;
18296
+ color: var(--jupiter-primary-color, #1976d2);
18297
+ cursor: pointer;
18298
+ }
18299
+
18300
+ .active-filter-banner-clear:hover {
18301
+ background: var(--jupiter-primary-color, #1976d2);
18302
+ color: #fff;
18303
+ }
18304
+
18305
+ .form-actions button,
18306
+ .error-popup-footer button {
16922
18307
  padding: 10px 20px;
16923
18308
  border: none;
16924
18309
  border-radius: 4px;
@@ -16975,12 +18360,14 @@ JupiterDynamicForm.styles = css`
16975
18360
  background: var(--jupiter-validate-color-dark, #2e7d32);
16976
18361
  }
16977
18362
 
16978
- .btn-preview {
16979
- background: var(--jupiter-primary-color, #1976d2);
16980
- color: white;
18363
+ .btn-primary--validating {
18364
+ opacity: 0.85;
18365
+ animation: jupiter-validating-pulse 1.5s ease-in-out infinite;
16981
18366
  }
16982
- .btn-preview:hover:not(:disabled) {
16983
- background: var(--jupiter-primary-color-dark, #1565c0);
18367
+
18368
+ @keyframes jupiter-validating-pulse {
18369
+ 0%, 100% { opacity: 0.85; }
18370
+ 50% { opacity: 1; }
16984
18371
  }
16985
18372
 
16986
18373
 
@@ -17062,6 +18449,7 @@ JupiterDynamicForm.styles = css`
17062
18449
  .roles-count {
17063
18450
  display: inline-flex;
17064
18451
  align-items: center;
18452
+ gap: 3px;
17065
18453
  background: var(--primaryTextColor, var(--jupiter-primary-color, #1976d2));
17066
18454
  color: var(--headerBgColor, white) !important;
17067
18455
  border-radius: 10px;
@@ -17073,6 +18461,14 @@ JupiterDynamicForm.styles = css`
17073
18461
  justify-content: center;
17074
18462
  }
17075
18463
 
18464
+ /* JDF-101 item 3: marks the badge as counting the advanced-filter-narrowed roles, not the
18465
+ Filter Roles selection, so it doesn't read as a plain (and here, smaller) role count. */
18466
+ .roles-count-filter-icon {
18467
+ width: 10px;
18468
+ height: 10px;
18469
+ fill: currentColor;
18470
+ }
18471
+
17076
18472
  .no-roles-message {
17077
18473
  padding: 40px;
17078
18474
  text-align: center;
@@ -17319,6 +18715,70 @@ JupiterDynamicForm.styles = css`
17319
18715
  background: var(--bg-color-2, var(--jupiter-background, #fff));
17320
18716
  }
17321
18717
 
18718
+ .side-panel-content-header {
18719
+ padding: 20px 24px 12px 24px;
18720
+ cursor: pointer;
18721
+ border-radius: 4px;
18722
+ }
18723
+
18724
+ .side-panel-content-header:hover {
18725
+ background: var(--jupiter-hover-color, rgba(25, 118, 210, 0.08));
18726
+ }
18727
+
18728
+ .side-panel-content-header:focus-visible {
18729
+ outline: 2px solid var(--jupiter-primary-color, #1976d2);
18730
+ outline-offset: 2px;
18731
+ }
18732
+
18733
+ .side-panel-content-position {
18734
+ font-size: 12px;
18735
+ font-weight: 600;
18736
+ letter-spacing: 0.02em;
18737
+ text-transform: uppercase;
18738
+ color: var(--jupiter-text-secondary, #757575);
18739
+ margin-bottom: 4px;
18740
+ }
18741
+
18742
+ .side-panel-content-title {
18743
+ font-size: 18px;
18744
+ font-weight: 600;
18745
+ margin: 0;
18746
+ color: var(--jupiter-text-primary, #333);
18747
+ }
18748
+
18749
+ /* JDF-093 Part B: stated once, here, rather than repeated on every input hint. */
18750
+ .side-panel-content-amount-scale {
18751
+ font-size: 13px;
18752
+ color: var(--jupiter-text-secondary, #757575);
18753
+ margin-top: 4px;
18754
+ }
18755
+
18756
+ .side-panel-nav-row {
18757
+ display: flex;
18758
+ align-items: center;
18759
+ justify-content: space-between;
18760
+ gap: 12px;
18761
+ padding: 4px 16px 16px 16px;
18762
+ }
18763
+
18764
+ .side-panel-nav-btn {
18765
+ background: none;
18766
+ border: 1px solid var(--jupiter-border-color, #e0e0e0);
18767
+ border-radius: 4px;
18768
+ padding: 8px 16px;
18769
+ font-size: 14px;
18770
+ color: var(--jupiter-primary-color, #1976d2);
18771
+ cursor: pointer;
18772
+ }
18773
+
18774
+ .side-panel-nav-btn:hover {
18775
+ background: var(--jupiter-hover-color, rgba(25, 118, 210, 0.08));
18776
+ }
18777
+
18778
+ .side-panel-nav-next {
18779
+ margin-left: auto;
18780
+ }
18781
+
17322
18782
  .side-panel-content .form-sections {
17323
18783
  padding: 0 24px 24px 24px;
17324
18784
  padding-top: 0;
@@ -17681,6 +19141,9 @@ __decorateClass([
17681
19141
  __decorateClass([
17682
19142
  n2({ type: String })
17683
19143
  ], JupiterDynamicForm.prototype, "reportingLanguage", 2);
19144
+ __decorateClass([
19145
+ n2({ type: String })
19146
+ ], JupiterDynamicForm.prototype, "numberLocale", 2);
17684
19147
  __decorateClass([
17685
19148
  n2({ type: String })
17686
19149
  ], JupiterDynamicForm.prototype, "display", 2);
@@ -17711,6 +19174,9 @@ __decorateClass([
17711
19174
  __decorateClass([
17712
19175
  n2({ type: Object, attribute: "master-data" })
17713
19176
  ], JupiterDynamicForm.prototype, "masterData", 2);
19177
+ __decorateClass([
19178
+ n2({ type: String, attribute: "prefilled-tooltip" })
19179
+ ], JupiterDynamicForm.prototype, "prefilledTooltip", 2);
17714
19180
  __decorateClass([
17715
19181
  n2({ type: Object, attribute: "dynaforms-metadata" })
17716
19182
  ], JupiterDynamicForm.prototype, "dynaformsMetadata", 2);
@@ -17789,6 +19255,12 @@ __decorateClass([
17789
19255
  __decorateClass([
17790
19256
  r()
17791
19257
  ], JupiterDynamicForm.prototype, "_showErrorPopup", 2);
19258
+ __decorateClass([
19259
+ r()
19260
+ ], JupiterDynamicForm.prototype, "_showTypedDimensionWarning", 2);
19261
+ __decorateClass([
19262
+ r()
19263
+ ], JupiterDynamicForm.prototype, "_typedDimensionWarningFacts", 2);
17792
19264
  __decorateClass([
17793
19265
  r()
17794
19266
  ], JupiterDynamicForm.prototype, "_submitDisabled", 2);
@@ -17849,6 +19321,9 @@ __decorateClass([
17849
19321
  __decorateClass([
17850
19322
  r()
17851
19323
  ], JupiterDynamicForm.prototype, "_validationStatus", 2);
19324
+ __decorateClass([
19325
+ r()
19326
+ ], JupiterDynamicForm.prototype, "_validationDotCount", 2);
17852
19327
  __decorateClass([
17853
19328
  r()
17854
19329
  ], JupiterDynamicForm.prototype, "_formulaValidationResults", 2);