ixbrl-viewer 1.4.1__py3-none-any.whl → 1.4.86__py3-none-any.whl

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.
Files changed (190) hide show
  1. iXBRLViewerPlugin/__init__.py +231 -127
  2. iXBRLViewerPlugin/_version.py +33 -3
  3. iXBRLViewerPlugin/constants.py +96 -2
  4. iXBRLViewerPlugin/featureConfig.py +8 -1
  5. iXBRLViewerPlugin/iXBRLViewer.py +356 -214
  6. iXBRLViewerPlugin/plugin.py +12 -0
  7. iXBRLViewerPlugin/ui.py +81 -50
  8. iXBRLViewerPlugin/viewer/dist/ixbrlviewer.js +1 -1
  9. iXBRLViewerPlugin/viewer/dist/ixbrlviewer.js.LICENSE.txt +12 -5
  10. iXBRLViewerPlugin/viewer/i18next-parser.config.js +1 -1
  11. iXBRLViewerPlugin/viewer/src/data/utr.json +1244 -0
  12. iXBRLViewerPlugin/viewer/src/html/fact-details.html +69 -38
  13. iXBRLViewerPlugin/viewer/src/html/footer-logo.html +4 -0
  14. iXBRLViewerPlugin/viewer/src/html/footnote-details.html +2 -2
  15. iXBRLViewerPlugin/viewer/src/html/inspector.html +352 -197
  16. iXBRLViewerPlugin/viewer/src/i18n/cy/balancetypes.json +1 -0
  17. iXBRLViewerPlugin/viewer/src/i18n/cy/currencies.json +13 -0
  18. iXBRLViewerPlugin/viewer/src/i18n/cy/datatypes.json +9 -0
  19. iXBRLViewerPlugin/viewer/src/i18n/cy/labelroles.json +24 -0
  20. iXBRLViewerPlugin/viewer/src/i18n/cy/referenceparts.json +10 -0
  21. iXBRLViewerPlugin/viewer/src/i18n/cy/scale.json +16 -0
  22. iXBRLViewerPlugin/viewer/src/i18n/cy/tooltips.json +17 -0
  23. iXBRLViewerPlugin/viewer/src/i18n/cy/translation.json +179 -0
  24. iXBRLViewerPlugin/viewer/src/i18n/da/balancetypes.json +4 -0
  25. iXBRLViewerPlugin/viewer/src/i18n/da/currencies.json +13 -0
  26. iXBRLViewerPlugin/viewer/src/i18n/da/datatypes.json +9 -0
  27. iXBRLViewerPlugin/viewer/src/i18n/da/labelroles.json +24 -0
  28. iXBRLViewerPlugin/viewer/src/i18n/da/referenceparts.json +10 -0
  29. iXBRLViewerPlugin/viewer/src/i18n/da/scale.json +15 -0
  30. iXBRLViewerPlugin/viewer/src/i18n/da/tooltips.json +17 -0
  31. iXBRLViewerPlugin/viewer/src/i18n/da/translation.json +179 -0
  32. iXBRLViewerPlugin/viewer/src/i18n/de/balancetypes.json +4 -0
  33. iXBRLViewerPlugin/viewer/src/i18n/de/currencies.json +13 -0
  34. iXBRLViewerPlugin/viewer/src/i18n/de/datatypes.json +9 -0
  35. iXBRLViewerPlugin/viewer/src/i18n/de/labelroles.json +24 -0
  36. iXBRLViewerPlugin/viewer/src/i18n/de/referenceparts.json +10 -0
  37. iXBRLViewerPlugin/viewer/src/i18n/de/scale.json +15 -0
  38. iXBRLViewerPlugin/viewer/src/i18n/de/tooltips.json +17 -0
  39. iXBRLViewerPlugin/viewer/src/i18n/de/translation.json +179 -0
  40. iXBRLViewerPlugin/viewer/src/i18n/en/balancetypes.json +4 -0
  41. iXBRLViewerPlugin/viewer/src/i18n/en/datatypes.json +10 -0
  42. iXBRLViewerPlugin/viewer/src/i18n/en/labelroles.json +4 -0
  43. iXBRLViewerPlugin/viewer/src/i18n/en/scale.json +16 -0
  44. iXBRLViewerPlugin/viewer/src/i18n/en/tooltips.json +17 -0
  45. iXBRLViewerPlugin/viewer/src/i18n/en/translation.json +73 -23
  46. iXBRLViewerPlugin/viewer/src/i18n/es/balancetypes.json +4 -0
  47. iXBRLViewerPlugin/viewer/src/i18n/es/datatypes.json +10 -0
  48. iXBRLViewerPlugin/viewer/src/i18n/es/labelroles.json +24 -0
  49. iXBRLViewerPlugin/viewer/src/i18n/es/scale.json +16 -0
  50. iXBRLViewerPlugin/viewer/src/i18n/es/tooltips.json +17 -0
  51. iXBRLViewerPlugin/viewer/src/i18n/es/translation.json +87 -37
  52. iXBRLViewerPlugin/viewer/src/i18n/fr/balancetypes.json +4 -0
  53. iXBRLViewerPlugin/viewer/src/i18n/fr/currencies.json +13 -0
  54. iXBRLViewerPlugin/viewer/src/i18n/fr/datatypes.json +9 -0
  55. iXBRLViewerPlugin/viewer/src/i18n/fr/labelroles.json +24 -0
  56. iXBRLViewerPlugin/viewer/src/i18n/fr/referenceparts.json +10 -0
  57. iXBRLViewerPlugin/viewer/src/i18n/fr/scale.json +15 -0
  58. iXBRLViewerPlugin/viewer/src/i18n/fr/tooltips.json +17 -0
  59. iXBRLViewerPlugin/viewer/src/i18n/fr/translation.json +179 -0
  60. iXBRLViewerPlugin/viewer/src/i18n/nl/balancetypes.json +4 -0
  61. iXBRLViewerPlugin/viewer/src/i18n/nl/currencies.json +13 -0
  62. iXBRLViewerPlugin/viewer/src/i18n/nl/datatypes.json +9 -0
  63. iXBRLViewerPlugin/viewer/src/i18n/nl/labelroles.json +24 -0
  64. iXBRLViewerPlugin/viewer/src/i18n/nl/referenceparts.json +10 -0
  65. iXBRLViewerPlugin/viewer/src/i18n/nl/scale.json +15 -0
  66. iXBRLViewerPlugin/viewer/src/i18n/nl/tooltips.json +17 -0
  67. iXBRLViewerPlugin/viewer/src/i18n/nl/translation.json +179 -0
  68. iXBRLViewerPlugin/viewer/src/i18n/uk/balancetypes.json +4 -0
  69. iXBRLViewerPlugin/viewer/src/i18n/uk/currencies.json +13 -0
  70. iXBRLViewerPlugin/viewer/src/i18n/uk/datatypes.json +9 -0
  71. iXBRLViewerPlugin/viewer/src/i18n/uk/labelroles.json +24 -0
  72. iXBRLViewerPlugin/viewer/src/i18n/uk/referenceparts.json +10 -0
  73. iXBRLViewerPlugin/viewer/src/i18n/uk/scale.json +15 -0
  74. iXBRLViewerPlugin/viewer/src/i18n/uk/tooltips.json +17 -0
  75. iXBRLViewerPlugin/viewer/src/i18n/uk/translation.json +179 -0
  76. iXBRLViewerPlugin/viewer/src/icons/calculator.svg +13 -0
  77. iXBRLViewerPlugin/viewer/src/icons/circle-cross.svg +11 -0
  78. iXBRLViewerPlugin/viewer/src/icons/circle-tick.svg +11 -0
  79. iXBRLViewerPlugin/viewer/src/icons/dark-mode.svg +4 -0
  80. iXBRLViewerPlugin/viewer/src/icons/dimension.svg +1 -5
  81. iXBRLViewerPlugin/viewer/src/icons/member.svg +2 -5
  82. iXBRLViewerPlugin/viewer/src/icons/multi-tag.svg +10 -0
  83. iXBRLViewerPlugin/viewer/src/img/arelle-dark.svg +179 -0
  84. iXBRLViewerPlugin/viewer/src/img/inline-viewer-dark.svg +59 -0
  85. iXBRLViewerPlugin/viewer/src/js/accordian.js +5 -4
  86. iXBRLViewerPlugin/viewer/src/js/aspect.js +29 -10
  87. iXBRLViewerPlugin/viewer/src/js/aspect.test.js +40 -31
  88. iXBRLViewerPlugin/viewer/src/js/balance.js +14 -0
  89. iXBRLViewerPlugin/viewer/src/js/calculation.js +213 -0
  90. iXBRLViewerPlugin/viewer/src/js/calculation.test.js +306 -0
  91. iXBRLViewerPlugin/viewer/src/js/calculationInspector.js +187 -0
  92. iXBRLViewerPlugin/viewer/src/js/chart.js +26 -24
  93. iXBRLViewerPlugin/viewer/src/js/chart.test.js +10 -9
  94. iXBRLViewerPlugin/viewer/src/js/concept.js +37 -4
  95. iXBRLViewerPlugin/viewer/src/js/concept.test.js +30 -6
  96. iXBRLViewerPlugin/viewer/src/js/datatype.js +20 -0
  97. iXBRLViewerPlugin/viewer/src/js/datatype.test.js +62 -0
  98. iXBRLViewerPlugin/viewer/src/js/dialog.js +6 -4
  99. iXBRLViewerPlugin/viewer/src/js/docOrderIndex.js +7 -7
  100. iXBRLViewerPlugin/viewer/src/js/fact.js +156 -59
  101. iXBRLViewerPlugin/viewer/src/js/fact.test.js +160 -29
  102. iXBRLViewerPlugin/viewer/src/js/factset.js +64 -15
  103. iXBRLViewerPlugin/viewer/src/js/factset.test.js +102 -31
  104. iXBRLViewerPlugin/viewer/src/js/footnote.js +8 -2
  105. iXBRLViewerPlugin/viewer/src/js/index.js +11 -3
  106. iXBRLViewerPlugin/viewer/src/js/inspector.js +747 -221
  107. iXBRLViewerPlugin/viewer/src/js/inspector.test.js +143 -25
  108. iXBRLViewerPlugin/viewer/src/js/interval.js +70 -0
  109. iXBRLViewerPlugin/viewer/src/js/interval.test.js +153 -0
  110. iXBRLViewerPlugin/viewer/src/js/ixbrlviewer.js +391 -262
  111. iXBRLViewerPlugin/viewer/src/js/ixbrlviewer.test.js +134 -20
  112. iXBRLViewerPlugin/viewer/src/js/ixnode.js +1 -1
  113. iXBRLViewerPlugin/viewer/src/js/menu.js +25 -7
  114. iXBRLViewerPlugin/viewer/src/js/number-matcher.js +7 -3
  115. iXBRLViewerPlugin/viewer/src/js/number-matcher.test.js +4 -0
  116. iXBRLViewerPlugin/viewer/src/js/outline.js +34 -13
  117. iXBRLViewerPlugin/viewer/src/js/outline.test.js +97 -91
  118. iXBRLViewerPlugin/viewer/src/js/period.js +0 -1
  119. iXBRLViewerPlugin/viewer/src/js/report.js +260 -351
  120. iXBRLViewerPlugin/viewer/src/js/report.test.js +95 -27
  121. iXBRLViewerPlugin/viewer/src/js/reportset.js +264 -0
  122. iXBRLViewerPlugin/viewer/src/js/reportset.test.js +357 -0
  123. iXBRLViewerPlugin/viewer/src/js/search.js +72 -38
  124. iXBRLViewerPlugin/viewer/src/js/search.test.js +184 -84
  125. iXBRLViewerPlugin/viewer/src/js/summary.js +34 -8
  126. iXBRLViewerPlugin/viewer/src/js/summary.test.js +69 -25
  127. iXBRLViewerPlugin/viewer/src/js/tableExport.js +9 -9
  128. iXBRLViewerPlugin/viewer/src/js/taxonomynamer.js +34 -0
  129. iXBRLViewerPlugin/viewer/src/js/taxonomynamer.test.js +32 -0
  130. iXBRLViewerPlugin/viewer/src/js/test-utils.js +46 -0
  131. iXBRLViewerPlugin/viewer/src/js/theme.js +50 -0
  132. iXBRLViewerPlugin/viewer/src/js/unit.js +90 -32
  133. iXBRLViewerPlugin/viewer/src/js/unit.test.js +62 -25
  134. iXBRLViewerPlugin/viewer/src/js/util.js +94 -0
  135. iXBRLViewerPlugin/viewer/src/js/util.test.js +33 -1
  136. iXBRLViewerPlugin/viewer/src/js/utr.js +27 -0
  137. iXBRLViewerPlugin/viewer/src/js/viewer.js +205 -181
  138. iXBRLViewerPlugin/viewer/src/js/viewerOptions.js +0 -2
  139. iXBRLViewerPlugin/viewer/src/less/accordian.less +10 -6
  140. iXBRLViewerPlugin/viewer/src/less/block-list.less +16 -5
  141. iXBRLViewerPlugin/viewer/src/less/calculation-inspector.less +83 -0
  142. iXBRLViewerPlugin/viewer/src/less/chart.less +8 -5
  143. iXBRLViewerPlugin/viewer/src/less/colours-dark-mode.less +40 -0
  144. iXBRLViewerPlugin/viewer/src/less/colours.less +32 -20
  145. iXBRLViewerPlugin/viewer/src/less/common.less +3 -3
  146. iXBRLViewerPlugin/viewer/src/less/components.less +6 -4
  147. iXBRLViewerPlugin/viewer/src/less/core.less +2 -0
  148. iXBRLViewerPlugin/viewer/src/less/dialog.less +21 -14
  149. iXBRLViewerPlugin/viewer/src/less/form-controls.less +33 -11
  150. iXBRLViewerPlugin/viewer/src/less/inspector.less +1045 -726
  151. iXBRLViewerPlugin/viewer/src/less/loader.less +2 -2
  152. iXBRLViewerPlugin/viewer/src/less/menu.less +33 -15
  153. iXBRLViewerPlugin/viewer/src/less/summary.less +16 -6
  154. iXBRLViewerPlugin/viewer/src/less/tabs.less +9 -9
  155. iXBRLViewerPlugin/viewer/src/less/text-block-viewer.less +2 -0
  156. iXBRLViewerPlugin/viewer/src/less/text-mixins.less +2 -1
  157. iXBRLViewerPlugin/viewer/src/less/validation-report.less +2 -3
  158. iXBRLViewerPlugin/viewer/src/less/viewer.less +105 -74
  159. iXBRLViewerPlugin/viewer/webpack.common.js +19 -9
  160. iXBRLViewerPlugin/xhtmlserialize.py +59 -45
  161. {ixbrl_viewer-1.4.1.dist-info → ixbrl_viewer-1.4.86.dist-info}/METADATA +181 -50
  162. ixbrl_viewer-1.4.86.dist-info/RECORD +217 -0
  163. {ixbrl_viewer-1.4.1.dist-info → ixbrl_viewer-1.4.86.dist-info}/WHEEL +1 -1
  164. ixbrl_viewer-1.4.1.dist-info/LICENSE → ixbrl_viewer-1.4.86.dist-info/licenses/LICENSE.md +8 -14
  165. {ixbrl_viewer-1.4.1.dist-info → ixbrl_viewer-1.4.86.dist-info}/top_level.txt +0 -1
  166. iXBRLViewerPlugin/viewer/src/js/calculations.js +0 -111
  167. iXBRLViewerPlugin/viewer/src/js/interact.min.js +0 -6
  168. ixbrl_viewer-1.4.1.dist-info/RECORD +0 -155
  169. tests/__init__.py +0 -0
  170. tests/puppeteer/framework/core_elements.js +0 -117
  171. tests/puppeteer/framework/page_objects/doc_frame.js +0 -105
  172. tests/puppeteer/framework/page_objects/fact_details_panel.js +0 -80
  173. tests/puppeteer/framework/page_objects/search_panel.js +0 -76
  174. tests/puppeteer/framework/page_objects/toolbar.js +0 -18
  175. tests/puppeteer/framework/utils.js +0 -3
  176. tests/puppeteer/framework/viewer_page.js +0 -103
  177. tests/puppeteer/puppeteer_test_run_via_intellij.jpg +0 -0
  178. tests/puppeteer/test_filings/filing_documents_smoke_test.zip +0 -0
  179. tests/puppeteer/test_filings/highlights.zip +0 -0
  180. tests/puppeteer/tests/fact_properties.test.js +0 -78
  181. tests/puppeteer/tests/highlight.test.js +0 -186
  182. tests/puppeteer/tests/search.test.js +0 -86
  183. tests/puppeteer/tools/generate.sh +0 -15
  184. tests/unit_tests/__init__.py +0 -0
  185. tests/unit_tests/iXBRLViewerPlugin/__init__.py +0 -0
  186. tests/unit_tests/iXBRLViewerPlugin/mock_arelle.py +0 -39
  187. tests/unit_tests/iXBRLViewerPlugin/test_iXBRLViewer.py +0 -641
  188. tests/unit_tests/iXBRLViewerPlugin/test_xhtmlserialize.py +0 -310
  189. {ixbrl_viewer-1.4.1.dist-info → ixbrl_viewer-1.4.86.dist-info}/entry_points.txt +0 -0
  190. {ixbrl_viewer-1.4.1.dist-info → ixbrl_viewer-1.4.86.dist-info/licenses}/NOTICE +0 -0
@@ -1,12 +1,11 @@
1
1
  // See COPYRIGHT.md for copyright information
2
2
 
3
3
  import $ from 'jquery'
4
- import { formatNumber, wrapLabel, truncateLabel, runGenerator } from "./util.js";
5
- import { ReportSearch } from "./search.js";
6
- import { Calculation } from "./calculations.js";
7
- import { IXBRLChart } from './chart.js';
8
4
  import i18next from 'i18next';
9
5
  import jqueryI18next from 'jquery-i18next';
6
+ import {formatNumber, wrapLabel, truncateLabel, runGenerator, SHOW_FACT, HIGHLIGHT_COLORS, viewerUniqueId, GLOSSARY_URL, FEATURE_HOME_LINK_URL, FEATURE_HOME_LINK_LABEL, FEATURE_SEARCH_ON_STARTUP, FEATURE_HIGHLIGHT_FACTS_ON_STARTUP, STORAGE_APP_LANGUAGE, STORAGE_HIGHLIGHT_FACTS, STORAGE_HOME_LINK_QUERY, FEATURE_HIDE_CALCULATION_MODE_OPTION} from "./util.js";
7
+ import { ReportSearch } from "./search.js";
8
+ import { IXBRLChart } from './chart.js';
10
9
  import { ViewerOptions } from './viewerOptions.js';
11
10
  import { Identifiers } from './identifiers.js';
12
11
  import { Menu } from './menu.js';
@@ -17,37 +16,57 @@ import { Footnote } from './footnote.js';
17
16
  import { ValidationReportDialog } from './validationreport.js';
18
17
  import { TextBlockViewerDialog } from './textblockviewer.js';
19
18
  import { MessageBox } from './messagebox.js';
20
- import { DocumentOutline } from './outline.js';
19
+ import { Calculation } from "./calculation.js";
20
+ import { CalculationInspector } from './calculationInspector.js';
21
+ import { ReportSetOutline } from './outline.js';
21
22
  import { DIMENSIONS_KEY, DocumentSummary, MEMBERS_KEY, PRIMARY_ITEMS_KEY, TOTAL_KEY } from './summary.js';
23
+ import { toggleTheme } from './theme.js';
22
24
 
23
25
  const SEARCH_PAGE_SIZE = 100
26
+ const SEARCH_FILTER_MULTISELECTS = {
27
+ periodFilter: "search-filter-period",
28
+ dimensionTypeFilter: "search-filter-dimension-type",
29
+ namespacesFilter: "search-filter-namespaces",
30
+ targetDocumentFilter: "search-filter-target-document",
31
+ scalesFilter:"search-filter-scales",
32
+ unitsFilter: "search-filter-units",
33
+ calculationsFilter: "search-filter-calculations",
34
+ dataTypesFilter: "search-filter-datatypes",
35
+ };
24
36
 
25
37
  export class Inspector {
26
38
  constructor(iv) {
27
39
  this._iv = iv;
28
40
  this._viewerOptions = new ViewerOptions()
29
41
  this._currentItem = null;
42
+ this._useCalc11 = true;
30
43
  }
31
44
 
32
45
  i18nInit() {
46
+ const langs = ["cy", "da", "de", "en", "es", "fr", "nl", "uk"];
47
+ const bundles = [
48
+ "translation",
49
+ "referenceParts",
50
+ "currencies",
51
+ "dataTypes",
52
+ "labelRoles",
53
+ "scale",
54
+ "balanceTypes",
55
+ "tooltips"
56
+ ];
33
57
  return i18next.init({
34
58
  lng: this.preferredLanguages()[0],
59
+ reloadOnLanguageChange: true,
35
60
  // Do not apply translations that are present but with an empty string
36
61
  returnEmptyString: false,
37
62
  fallbackLng: 'en',
38
63
  debug: false,
39
- resources: {
40
- en: {
41
- translation: require('../i18n/en/translation.json'),
42
- referenceParts: require('../i18n/en/referenceparts.json'),
43
- currencies: require('../i18n/en/currencies.json')
44
- },
45
- es: {
46
- translation: require('../i18n/es/translation.json'),
47
- referenceParts: require('../i18n/es/referenceparts.json'),
48
- currencies: require('../i18n/es/currencies.json')
49
- }
50
- }
64
+ resources:
65
+ Object.fromEntries(
66
+ langs.map(l => [l, Object.fromEntries(
67
+ bundles.map(n => [n, require(`../i18n/${l}/${n.toLowerCase()}.json`)]))
68
+ ])
69
+ )
51
70
  }).then((t) => {
52
71
  jqueryI18next.init(i18next, $, {
53
72
  tName: 't', // --> appends $.t = i18next.t
@@ -61,15 +80,15 @@ export class Inspector {
61
80
  });
62
81
  }
63
82
 
64
- initialize(report, viewer) {
83
+ initialize(reportSet, viewer) {
65
84
  const inspector = this;
66
85
  this._viewer = viewer;
67
86
  return new Promise(function (resolve, reject) {
68
87
  inspector._chart = new IXBRLChart();
69
- inspector._report = report;
88
+ inspector._reportSet = reportSet;
70
89
  inspector.i18nInit().then((t) => {
71
90
 
72
- $(".collapsible-header").on("click", function () {
91
+ $(".collapsible-header button:first-of-type").on("click", function () {
73
92
  const d = $(this).closest(".collapsible-section");
74
93
  d.toggleClass("collapsed");
75
94
  if (d.hasClass("collapsed")) {
@@ -86,56 +105,98 @@ export class Inspector {
86
105
  }
87
106
  }
88
107
  });
89
- $("#inspector .controls .search-button").on("click", function () {
90
- $(this).closest("#inspector").removeClass(["summary-mode", "outline-mode"]).toggleClass("search-mode");
108
+ $("#inspector .controls .search-button").on("click", () => inspector.inspectorMode("search-mode", true));
109
+ $("#inspector .controls .summary-button").on("click", () => inspector.inspectorMode("summary-mode", true));
110
+ $("#inspector .controls .outline-button").on("click", () => inspector.inspectorMode("outline-mode", true));
111
+ $("#inspector .back").on("click", () => inspector.popInspectorMode());
112
+ $(".popup-trigger").on("mouseenter", function () {
113
+ $(this).find(".popup-content").show()
114
+ }).on("mouseleave", function () {
115
+ $(this).find(".popup-content").hide()
91
116
  });
92
- $("#inspector .controls .summary-button").on("click", function () {
93
- $(this).closest("#inspector").removeClass(["outline-mode", "search-mode"]).toggleClass("summary-mode");
94
- });
95
- $("#inspector .controls .outline-button").on("click", function () {
96
- $(this).closest("#inspector").removeClass(["summary-mode", "search-mode"]).toggleClass("outline-mode");
97
- });
98
- $("#inspector-head .back").on("click", function () {
99
- $(this).closest("#inspector").removeClass(["summary-mode", "outline-mode", "search-mode"]);
100
- });
101
- $(".popup-trigger").hover(function () { $(this).find(".popup-content").show() }, function () { $(this).find(".popup-content").hide() });
102
117
  $("#inspector").on("click", ".clipboard-copy", function () {
103
118
  navigator.clipboard.writeText($(this).data("cb-text"));
104
119
  });
120
+
121
+ inspector.initializeTooltips();
122
+
105
123
  inspector._toolbarMenu = new Menu($("#toolbar-highlight-menu"));
106
124
  inspector.buildToolbarHighlightMenu();
107
125
 
108
126
  inspector._optionsMenu = new Menu($("#display-options-menu"));
109
127
  inspector.buildDisplayOptionsMenu();
110
128
 
129
+ inspector.buildHomeLink()
130
+
111
131
  $("#ixv").localize();
112
132
 
113
133
  // Listen to messages posted to this window
114
134
  $(window).on("message", (e) => inspector.handleMessage(e));
115
- report.setViewerOptions(inspector._viewerOptions);
116
- inspector.summary = new DocumentSummary(report);
135
+ reportSet.viewerOptions = inspector._viewerOptions;
136
+ inspector.summary = new DocumentSummary(reportSet);
117
137
  inspector.createSummary()
118
- inspector.outline = new DocumentOutline(report);
138
+ inspector.outline = new ReportSetOutline(reportSet);
119
139
  inspector.createOutline();
120
140
  inspector._iv.setProgress(i18next.t("inspector.initializing")).then(() => {
121
- inspector._search = new ReportSearch(report);
122
- inspector.buildDisplayOptionsMenu();
123
- inspector.buildToolbarHighlightMenu();
124
- inspector.buildHighlightKey();
141
+ inspector._search = new ReportSearch(reportSet);
142
+ inspector.handleFactDeepLink();
143
+ inspector.rebuildViewer();
125
144
  inspector.setupValidationReportIcon();
126
145
  inspector.initializeViewer();
146
+ inspector.doInitialSelection();
127
147
  resolve();
128
148
  });
129
149
  });
130
150
  });
131
151
  }
132
152
 
153
+ initializeTooltips() {
154
+ $("html").on("click", e =>
155
+ this.hideTooltip()
156
+ );
157
+ $("#inspector .inspector-body").on("scroll", e =>
158
+ this.hideTooltip()
159
+ );
160
+ $(document).on("keyup", (e) => {
161
+ if (e.keyCode == 27) {
162
+ this.hideTooltip();
163
+ }
164
+ });
165
+ $("#ixv").on("click", ".tooltip-icon", (e) => {
166
+ this.toggleTooltip($(e.currentTarget));
167
+ e.stopPropagation();
168
+ });
169
+
170
+ let tooltipHoverCount = 0;
171
+ $("#ixv").on("mouseenter", ".tooltip-icon", e => {
172
+ tooltipHoverCount++;
173
+ setTimeout(t => {
174
+ if (tooltipHoverCount > 0) {
175
+ this.showTooltip($(e.currentTarget), true);
176
+ }
177
+ }, 250);
178
+ });
179
+ $("#ixv").on("mouseenter", "#tooltip", e => tooltipHoverCount++);
180
+ $("#ixv").on("mouseleave", "#tooltip, .tooltip-icon", e => {
181
+ tooltipHoverCount--;
182
+ setTimeout(e => {
183
+ if (tooltipHoverCount == 0) {
184
+ this.hideTooltip(true);
185
+ }
186
+ }, 500);
187
+ });
188
+ }
189
+
133
190
  initializeViewer() {
134
- this._viewer.onSelect.add((id, eltSet, byClick) => this.selectItem(id, eltSet, byClick));
191
+ this._viewer.onSelect.add((vuid, eltSet, byClick) => this.selectItem(vuid, eltSet, byClick));
135
192
  this._viewer.onMouseEnter.add((id) => this.viewerMouseEnter(id));
136
193
  this._viewer.onMouseLeave.add(id => this.viewerMouseLeave(id));
137
- $('.ixbrl-next-tag').click(() => this._viewer.selectNextTag(this._currentItem));
138
- $('.ixbrl-prev-tag').click(() => this._viewer.selectPrevTag(this._currentItem));
194
+ $('.ixbrl-next-tag').on("click", () => this._viewer.selectNextTag(this._currentItem));
195
+ $('.ixbrl-prev-tag').on("click", () => this._viewer.selectPrevTag(this._currentItem));
196
+ $('#toggle-dark-mode')
197
+ .attr('title', i18next.t('toolbar.toggleDarkMode'))
198
+ .attr('aria-label', i18next.t('toolbar.toggleDarkMode'))
199
+ .on('click', toggleTheme);
139
200
  }
140
201
 
141
202
  postLoadAsync() {
@@ -145,10 +206,26 @@ export class Inspector {
145
206
  /*
146
207
  * Check for fragment identifier pointing to a specific fact and select it if
147
208
  * present.
209
+ *
210
+ * Legacy format: #f-FACT_ID
211
+ * New format: #fN-FACT_ID where N is report index
212
+ * For N == 0, we use the legacy format.
213
+ *
148
214
  */
149
215
  handleFactDeepLink() {
150
- if (location.hash.startsWith("#f-")) {
151
- this.selectItem(location.hash.slice(3));
216
+ const match = location.hash.match(/^#f([0-9]+)?-(.*)$/);
217
+ if (match !== null) {
218
+ const reportId = match[1] ?? 0;
219
+ const id = viewerUniqueId(reportId, match[2]);
220
+ if (this._reportSet.getItemById(id) !== undefined) {
221
+ this.selectItem(id);
222
+ }
223
+ }
224
+ }
225
+
226
+ doInitialSelection() {
227
+ if (!this._currentItem && this._iv.isFeatureEnabled(FEATURE_SEARCH_ON_STARTUP)) {
228
+ this.inspectorMode("search-mode");
152
229
  }
153
230
  }
154
231
 
@@ -163,9 +240,14 @@ export class Inspector {
163
240
  // messages to itself when exporting files.
164
241
  return;
165
242
  }
166
-
167
- if (data.task == 'SHOW_FACT') {
168
- this.selectItem(data.factId);
243
+ const task = data["task"];
244
+ if (task === SHOW_FACT) {
245
+ let docSetId = Number(data["docSetId"]);
246
+ if (!docSetId) { // Handles NaN
247
+ docSetId = 0;
248
+ }
249
+ const vuid = viewerUniqueId(docSetId, data['factId']);
250
+ this.selectItem(vuid);
169
251
  }
170
252
  else {
171
253
  console.log("Not handling unsupported task message: " + jsonString);
@@ -174,7 +256,9 @@ export class Inspector {
174
256
 
175
257
  updateURLFragment() {
176
258
  if (this._currentItem) {
177
- location.hash = "#f-" + this._currentItem.id;
259
+ // Don't include report number for first report for compatibility
260
+ // with legacy fragments
261
+ location.hash = "#f" + this._currentItem.vuid.replace(/^0-/, "-");
178
262
  }
179
263
  else {
180
264
  location.hash = "";
@@ -183,21 +267,108 @@ export class Inspector {
183
267
 
184
268
  buildDisplayOptionsMenu() {
185
269
  this._optionsMenu.reset();
186
- if (this._report) {
187
- const dl = this.selectDefaultLanguage();
188
- this._optionsMenu.addCheckboxGroup(this._report.availableLanguages(), this._report.languageNames(), dl, (lang) => { this.setLanguage(lang); this.update() }, "select-language");
189
- this.setLanguage(dl);
190
- if (this._report.filingDocuments()) {
191
- this._optionsMenu.addDownloadButton("Download filing documents", this._report.filingDocuments())
270
+ if (this._reportSet) {
271
+ // Doc language
272
+ const defaultDocLang = this.selectDefaultLanguage();
273
+ const docLangs = this._reportSet.availableLanguages();
274
+ const docLangNames = new Intl.DisplayNames(this.preferredLanguages(), { "type": "language" });
275
+
276
+ this._optionsMenu.addLabel(i18next.t("menu.documentLanguage"));
277
+ this._optionsMenu.addCheckboxGroup(
278
+ docLangs,
279
+ Object.fromEntries(docLangs.map((l) => [l, docLangNames.of(l)])),
280
+ defaultDocLang,
281
+ (lang) => { this.setDocumentLanguage(lang); this.update() },
282
+ "select-language"
283
+ );
284
+ this.setDocumentLanguage(defaultDocLang);
285
+
286
+ // Application language
287
+ const defaultAppLang = i18next.language.substring(0, 2);
288
+ const appLangs = Object.keys(i18next.options.resources);
289
+ const appLangNames = new Intl.DisplayNames(this.preferredLanguages(), { "type": "language" });
290
+
291
+ this._optionsMenu.addLabel(i18next.t("menu.applicationLanguage"));
292
+ this._optionsMenu.addCheckboxGroup(
293
+ appLangs,
294
+ Object.fromEntries(appLangs.map((l) => [l, appLangNames.of(l)])),
295
+ defaultAppLang,
296
+ (lang) => { this.changeApplicationLanguage(lang); },
297
+ "select-user-language"
298
+ );
299
+
300
+ // Actions
301
+ if (this._reportSet.filingDocuments()) {
302
+ this._optionsMenu.addLabel(i18next.t("menu.actions"));
303
+ this._optionsMenu.addDownloadButton("Download filing documents", this._reportSet.filingDocuments())
304
+ }
305
+
306
+ // Options
307
+ if (this._reportSet.usesCalculations() && !this._iv.isFeatureEnabled(FEATURE_HIDE_CALCULATION_MODE_OPTION)) {
308
+ this._optionsMenu.addLabel(i18next.t("menu.options"));
309
+ this._optionsMenu.addCheckboxItem(i18next.t("calculation.useCalculations11"), (useCalc11) => this.setCalculationMode(useCalc11), "calculation-mode", "select-language", this._useCalc11);
310
+ }
311
+ }
312
+ let helpLinks = {}
313
+ let supportLinkUrl = this._iv.getSupportLinkUrl();
314
+ if (supportLinkUrl) {
315
+ helpLinks[i18next.t("menu.contactUs")] = supportLinkUrl;
316
+ }
317
+ let surveyLinkUrl = this._iv.getSurveyLinkUrl();
318
+ if (surveyLinkUrl) {
319
+ helpLinks[i18next.t("menu.survey")] = surveyLinkUrl;
320
+ }
321
+ if (Object.entries(helpLinks).length > 0) {
322
+ this._optionsMenu.addLabel(i18next.t("menu.help"));
323
+ for (const [label, value] of Object.entries(helpLinks)) {
324
+ this._optionsMenu.addLink(label, value);
192
325
  }
193
326
  }
194
327
  this._iv.callPluginMethod("extendDisplayOptionsMenu", this._optionsMenu);
195
328
  }
196
329
 
330
+ buildHomeLink() {
331
+ $('#top-bar #home-link').remove();
332
+ if (!this._iv.isStaticFeatureEnabled(FEATURE_HOME_LINK_URL)) {
333
+ return;
334
+ }
335
+ $("#top-bar img.header").remove();
336
+ let homeLinkUrl = this._iv.getStaticFeatureValue(FEATURE_HOME_LINK_URL);
337
+ let homeLinkText;
338
+ if (this._iv.isStaticFeatureEnabled(FEATURE_HOME_LINK_LABEL)) {
339
+ homeLinkText = this._iv.getStaticFeatureValue(FEATURE_HOME_LINK_LABEL);
340
+ } else {
341
+ homeLinkText = i18next.t("toolbar.homePage");
342
+ }
343
+ const query = sessionStorage.getItem(STORAGE_HOME_LINK_QUERY);
344
+ if (query) {
345
+ if (!homeLinkUrl.includes("?")) {
346
+ homeLinkUrl += "?";
347
+ } else {
348
+ homeLinkUrl += "&";
349
+ }
350
+ homeLinkUrl += query;
351
+ }
352
+ const homeLink = $('<a></a>')
353
+ .attr('href', homeLinkUrl)
354
+ .attr('id', 'home-link')
355
+ .text(homeLinkText);
356
+ $('#top-bar').prepend(homeLink);
357
+ }
358
+
359
+
360
+ highlightTagsOnStartup() {
361
+ const pref = window.localStorage.getItem(STORAGE_HIGHLIGHT_FACTS);
362
+ if (pref !== null) {
363
+ return JSON.parse(pref);
364
+ }
365
+ return this._iv.isFeatureEnabled(FEATURE_HIGHLIGHT_FACTS_ON_STARTUP);
366
+ }
367
+
197
368
  buildToolbarHighlightMenu() {
198
369
  const iv = this._iv;
199
370
  this._toolbarMenu.reset();
200
- this._toolbarMenu.addCheckboxItem(i18next.t("toolbar.xbrlElements"), (checked) => this.highlightAllTags(checked), "highlight-tags", null, this._iv.options.highlightTagsOnStartup);
371
+ this._toolbarMenu.addCheckboxItem(i18next.t("toolbar.xbrlElements"), (checked, explicitClick) => this.highlightAllTags(checked, explicitClick), "highlight-tags", null, this.highlightTagsOnStartup())
201
372
  if (iv.isReviewModeEnabled()) {
202
373
  this._toolbarMenu.addCheckboxItem("Untagged Numbers", function (checked) {
203
374
  const body = iv.viewer.contents().find("body");
@@ -232,28 +403,98 @@ export class Inspector {
232
403
  "Untagged Dates",
233
404
  ]
234
405
  } else {
235
- key = this._report.namespaceGroups();
406
+ key = this._reportSet.namespaceGroups().map(p => this._reportSet.preferredPrefix(p));
236
407
  }
237
408
  this._iv.callPluginMethod("extendHighlightKey", key);
238
409
 
239
410
  for (const [i, name] of key.entries()) {
240
411
  $("<div>")
241
412
  .addClass("item")
242
- .append($("<span></span>").addClass("sample").addClass("sample-" + i))
413
+ .append($("<span></span>").addClass("sample").addClass("sample-" + (i % HIGHLIGHT_COLORS)))
243
414
  .append($("<span></span>").text(name))
244
415
  .appendTo($(".highlight-key .items"));
245
416
  }
246
417
  }
247
418
 
248
- highlightAllTags(checked) {
249
- this._viewer.highlightAllTags(checked, this._report.namespaceGroups());
419
+ buildUserGuideLink() {
420
+ $('.top-bar-controls #user-guide-link').remove();
421
+ const userGuideUrl = this._iv.getGuideLinkUrl();
422
+ const userGuideLink = $('<a></a>')
423
+ .attr('href', userGuideUrl)
424
+ .attr('id', 'user-guide-link')
425
+ .attr('target', '_blank')
426
+ .text(i18next.t("menu.userGuide"));
427
+ userGuideLink.insertBefore('#toggle-dark-mode');
428
+ }
429
+
430
+ changeApplicationLanguage(lang) {
431
+ localStorage.setItem(STORAGE_APP_LANGUAGE, lang);
432
+ i18next.changeLanguage(lang);
433
+ this.rebuildViewer();
434
+ }
435
+
436
+ rebuildViewer() {
437
+ $("#ixv").localize();
438
+ $('html').attr('lang', i18next.resolvedLanguage);
439
+ this.buildDisplayOptionsMenu();
440
+ this.buildHomeLink()
441
+ this.buildToolbarHighlightMenu();
442
+ this.buildHighlightKey();
443
+ this.buildUserGuideLink();
444
+ this.update();
445
+ this.search();
446
+ }
447
+
448
+ inspectorMode(mode, toggle) {
449
+ const allModes = ["summary-mode", "outline-mode", "search-mode"];
450
+ const i = $("#inspector").removeClass(allModes.filter(m => m !== mode));
451
+ if (mode === undefined) {
452
+ this._prevInspectorMode = undefined;
453
+ return;
454
+ }
455
+ if (toggle) {
456
+ i.toggleClass(mode);
457
+ }
458
+ else {
459
+ i.addClass(mode);
460
+ }
461
+ }
462
+
463
+ /*
464
+ * Controls where the "back" button takes you. We only set this when you
465
+ * follow a link that switches between modes, otherwise back just takes you
466
+ * back to the main inspector mode.
467
+ */
468
+ pushInspectorMode(newMode, oldMode) {
469
+ this._prevInspectorMode = oldMode;
470
+ this.inspectorMode(newMode);
471
+ }
472
+
473
+ popInspectorMode() {
474
+ this.inspectorMode(this._prevInspectorMode);
475
+ this._prevInspectorMode = undefined;
476
+ }
477
+
478
+ setCalculationMode(useCalc11) {
479
+ this._useCalc11 = useCalc11;
480
+ if (this._currentItem instanceof Fact) {
481
+ this.updateCalculation(this._currentItem);
482
+ }
483
+ }
484
+
485
+ highlightAllTags(checked, explicitClick) {
486
+ if (explicitClick) {
487
+ window.localStorage.setItem(STORAGE_HIGHLIGHT_FACTS, JSON.stringify(checked));
488
+ }
489
+ this._viewer.highlightAllTags(checked, this._reportSet.namespaceGroups());
250
490
  }
251
491
 
252
492
  factListRow(f) {
253
- const row = $('<div class="fact-list-item"></div>')
254
- .click(() => this.selectItem(f.id))
255
- .dblclick(() => $('#inspector').removeClass("search-mode"))
256
- .mousedown((e) => {
493
+ const row = $('<button class="fact-list-item"></button>')
494
+ // soft focus - highlight the fact, but don't close the search results
495
+ .on("click", () => this.selectItem(f.vuid, undefined, undefined, true))
496
+ .on("dblclick", () => this.selectItem(f.vuid))
497
+ .on("mousedown", (e) => {
257
498
  /* Prevents text selection via double click without
258
499
  * disabling click+drag text selection (which user-select:
259
500
  * none would )
@@ -262,38 +503,48 @@ export class Inspector {
262
503
  e.preventDefault()
263
504
  }
264
505
  })
265
- .mouseenter(() => this._viewer.linkedHighlightFact(f))
266
- .mouseleave(() => this._viewer.clearLinkedHighlightFact(f))
267
- .data('ivid', f.id);
268
- $('<div class="select-icon"></div>')
269
- .click(() => {
270
- this.selectItem(f.id);
271
- $('#inspector').removeClass("search-mode");
506
+ .on("mouseenter", () => this._viewer.linkedHighlightFact(f))
507
+ .on("mouseleave", () => this._viewer.clearLinkedHighlightFact(f))
508
+ .data('ivid', f.vuid);
509
+ $('<button class="select-icon"></button>')
510
+ .attr("title", i18next.t("search.viewFact"))
511
+ .on("click", () => {
512
+ this.selectItem(f.vuid);
272
513
  })
273
514
  .appendTo(row)
274
- $('<div class="title"></div>')
275
- .text(f.getLabelOrName("std"))
515
+ this._setLabelWithLang($('<div class="title"></div>'), f.getLabelOrNameAndLang("std"))
516
+ .appendTo(row);
517
+ const dt = f.concept().dataType();
518
+ if (dt !== undefined) {
519
+ $('<div class="datatype"></div>')
520
+ .text(dt.label())
276
521
  .appendTo(row);
522
+ }
277
523
  $('<div class="dimension"></div>')
278
524
  .text(f.period().toString())
279
525
  .appendTo(row);
280
526
 
281
527
  for (const aspect of f.aspects()) {
282
528
  if (aspect.isTaxonomyDefined() && !aspect.isNil()) {
283
- $('<div class="dimension"></div>')
284
- .text(aspect.valueLabel())
529
+ this._setLabelWithLang($('<div class="dimension"></div>'), aspect.valueLabelAndLang())
285
530
  .appendTo(row);
286
531
  }
287
532
  }
533
+ const tags = $("<div></div>").addClass("block-list-item-tags").appendTo(row);
534
+ if (f.targetDocument() !== null) {
535
+ $('<div class="hidden"></div>')
536
+ .text(f.targetDocument())
537
+ .appendTo(tags);
538
+ }
288
539
  if (f.isHidden()) {
289
540
  $('<div class="hidden"></div>')
290
541
  .text(i18next.t("search.hiddenFact"))
291
- .appendTo(row);
542
+ .appendTo(tags);
292
543
  }
293
544
  else if (f.isHTMLHidden()) {
294
545
  $('<div class="hidden"></div>')
295
546
  .text(i18next.t("search.concealedFact"))
296
- .appendTo(row);
547
+ .appendTo(tags);
297
548
  }
298
549
  return row;
299
550
  }
@@ -315,24 +566,39 @@ export class Inspector {
315
566
  searchSpec() {
316
567
  const spec = {};
317
568
  spec.searchString = $('#ixbrl-search').val();
318
- spec.showVisibleFacts = $('#search-visible-fact-filter').prop('checked');
319
- spec.showHiddenFacts = $('#search-hidden-fact-filter').prop('checked');
320
- spec.namespacesFilter = $('#search-filter-namespaces select').val();
321
- spec.unitsFilter = $('#search-filter-units select').val();
322
- spec.scalesFilter = $('#search-filter-scales select').val();
323
- spec.periodFilter = $('#search-filter-period select').val();
569
+ spec.visibilityFilter = $('#search-filter-visibility').val();
570
+ spec.showMandatoryFacts = $('#search-mandatory-fact-filter').prop('checked');
324
571
  spec.conceptTypeFilter = $('#search-filter-concept-type').val();
572
+ for (const [key, name] of Object.entries(SEARCH_FILTER_MULTISELECTS)) {
573
+ spec[key] = $(`#${name} select`).val();
574
+ }
575
+
576
+ const selectedDataTypes = this._reportSet.getUsedConceptDataTypes().filter(d => spec.dataTypesFilter.includes(d.dataType.name));
577
+ if (
578
+ (spec.conceptTypeFilter == 'numeric' && selectedDataTypes.some(dt => !dt.isNumeric)) ||
579
+ (spec.conceptTypeFilter == 'text' && selectedDataTypes.some(dt => dt.isNumeric))) {
580
+ $("#search-filter-datatypes .datatype-conflict-warning").show();
581
+ }
582
+ else {
583
+ $("#search-filter-datatypes .datatype-conflict-warning").hide();
584
+ }
325
585
  spec.factValueFilter = $('#search-filter-fact-value').val();
326
- spec.calculationsFilter = $('#search-filter-calculations select').val();
327
- spec.dimensionTypeFilter = $('#search-filter-dimension-type select').val();
328
586
  return spec;
329
587
  }
330
588
 
589
+ hasActiveSearchFilters(searchSpec) {
590
+ return Object.keys(SEARCH_FILTER_MULTISELECTS).some(k => searchSpec[k].length > 0) ||
591
+ searchSpec.visibilityFilter !== '*' ||
592
+ searchSpec.showMandatoryFacts ||
593
+ searchSpec.conceptTypeFilter !== "*" ||
594
+ searchSpec.factValueFilter !== "*" ;
595
+ }
596
+
331
597
  setupSearchControls(viewer) {
332
598
  const inspector = this;
333
- $('.search-controls input, .search-controls select').change(() => this.search());
334
- $(".search-controls div.filter-toggle").click(() => $(".search-controls").toggleClass('show-filters'));
335
- $(".search-controls .search-filters .reset").click(() => this.resetSearchFilters());
599
+ $('.search-controls input, .search-controls select').on("change", () => this.search());
600
+ $(".search-controls button.filter-toggle").on("click", () => $(".search-controls").toggleClass('show-filters'));
601
+ $(".search-controls .reset").on("click", () => this.resetSearchFilters());
336
602
  $(".search-controls .search-filters .reset-multiselect").on("click", function () {
337
603
  $(this).siblings().children('select option:selected').prop('selected', false);
338
604
  inspector.search();
@@ -343,16 +609,39 @@ export class Inspector {
343
609
  .text(this._search.periods[key])
344
610
  .appendTo('#search-filter-period select');
345
611
  }
346
- for (const prefix of this._report.getUsedPrefixes()) {
612
+ for (const prefix of this._reportSet.getUsedConceptPrefixes()) {
347
613
  $("<option>")
348
614
  .attr("value", prefix)
349
- .text(`${prefix} (${this._report.prefixMap()[prefix]})`)
615
+ .text(`${this._reportSet.preferredPrefix(prefix)} (${this._reportSet.prefixMap()[prefix]})`)
350
616
  .appendTo('#search-filter-namespaces select');
351
617
  }
352
- for (const unit of this._report.getUsedUnits()) {
618
+ if (this._reportSet.getUsedConceptDataTypes().length > 0) {
619
+ for (const dataType of this._reportSet.getUsedConceptDataTypes()) {
620
+ $("<option>")
621
+ .attr("value", dataType.dataType.name)
622
+ .text(dataType.dataType.label())
623
+ .appendTo('#search-filter-datatypes select');
624
+ }
625
+ }
626
+ else {
627
+ $('#search-filter-datatypes').hide();
628
+ }
629
+ const targetDocuments = Array.from(this._reportSet.getTargetDocuments());
630
+ if (targetDocuments.length == 1 && targetDocuments[0] == null) {
631
+ $('#search-filter-target-document').hide();
632
+ }
633
+ else {
634
+ for (const targetDocument of targetDocuments) {
635
+ $("<option>")
636
+ .attr("value", targetDocument ?? ':default')
637
+ .text(targetDocument ?? `<${i18next.t("search.default")}>`)
638
+ .appendTo('#search-filter-target-document select');
639
+ }
640
+ }
641
+ for (const unit of this._reportSet.getUsedUnits()) {
353
642
  $("<option>")
354
643
  .attr("value", unit)
355
- .text(`${this._report.getUnit(unit)?.label()} (${unit})`)
644
+ .text(`${this._reportSet.getUnit(unit)?.label()} (${unit})`)
356
645
  .appendTo('#search-filter-units select');
357
646
  }
358
647
  const scalesOptions = this._getScalesOptions();
@@ -366,7 +655,7 @@ export class Inspector {
366
655
 
367
656
  _getScalesOptions() {
368
657
  const scalesOptions = {}
369
- const usedScalesMap = this._report.getUsedScalesMap();
658
+ const usedScalesMap = this._reportSet.getUsedScalesMap();
370
659
  Object.keys(usedScalesMap).sort().forEach(scale => {
371
660
  const labels = Array.from(usedScalesMap[scale]).sort();
372
661
  if (labels.length > 0) {
@@ -379,17 +668,16 @@ export class Inspector {
379
668
  return scalesOptions;
380
669
  }
381
670
 
382
- resetSearchFilters() {
671
+ resetSearchFilters(defaults) {
672
+ defaults = defaults ?? {};
383
673
  $("#search-filter-period select option:selected").prop("selected", false);
674
+ $("#search-filter-visibility").val(defaults.visibility ?? "*");
384
675
  $("#search-filter-concept-type").val("*");
385
676
  $("#search-filter-fact-value").val("*");
386
- $("#search-filter-calculations select option:selected").prop("selected", false);
387
- $("#search-filter-dimension-type select option:selected").prop("selected", false);
388
- $("#search-hidden-fact-filter").prop("checked", true);
389
- $("#search-visible-fact-filter").prop("checked", true);
390
- $("#search-filter-namespaces select option:selected").prop("selected", false);
391
- $("#search-filter-units select option:selected").prop("selected", false);
392
- $("#search-filter-scales select option:selected").prop("selected", false);
677
+ $("#search-mandatory-fact-filter").prop("checked", defaults.mandatoryFacts ?? false);
678
+ for (const name of Object.values(SEARCH_FILTER_MULTISELECTS)) {
679
+ $(`#${name} select option:selected`).prop("selected", false);
680
+ }
393
681
  this.search();
394
682
  }
395
683
 
@@ -400,14 +688,20 @@ export class Inspector {
400
688
  this.search();
401
689
  }
402
690
 
403
- search () {
691
+ search() {
404
692
  const spec = this.searchSpec();
693
+ if (this.hasActiveSearchFilters(spec)) {
694
+ $("#inspector .search-controls").addClass("active-filters");
695
+ }
696
+ else {
697
+ $("#inspector .search-controls").removeClass("active-filters");
698
+ }
405
699
  const results = this._search.search(spec);
406
700
  if (results === undefined) {
407
701
  return;
408
702
  }
409
703
  const container = $('#inspector .search-results .results');
410
- $('div', container).remove();
704
+ container.empty();
411
705
  this._viewer.clearRelatedHighlighting();
412
706
  const overlay = $('#inspector .search-results .search-overlay');
413
707
  if (results.length > 0) {
@@ -419,17 +713,14 @@ export class Inspector {
419
713
  $(".text", overlay).text(i18next.t("search.tryAgainDifferentKeywords"));
420
714
  overlay.show();
421
715
  }
422
- $("#matching-concepts-count").text(results.length);
716
+ $("#matching-facts-summary").text(i18next.t("search.matchingFactsSummary", {nMatches: results.length, nTotal: this._reportSet.facts().length}));
423
717
  /* Don't highlight search results if there's no search string */
424
718
  if (spec.searchString != "") {
425
719
  this._viewer.highlightRelatedFacts(results.map(r => r.fact));
426
720
  }
427
- this.updateMultiSelectSubheader('search-filter-scales');
428
- this.updateMultiSelectSubheader('search-filter-units');
429
- this.updateMultiSelectSubheader('search-filter-namespaces');
430
- this.updateMultiSelectSubheader('search-filter-dimension-type');
431
- this.updateMultiSelectSubheader('search-filter-calculations');
432
- this.updateMultiSelectSubheader('search-filter-period');
721
+ for (const name of Object.values(SEARCH_FILTER_MULTISELECTS)) {
722
+ this.updateMultiSelectSubheader(name);
723
+ }
433
724
  }
434
725
 
435
726
  updateMultiSelectSubheader(id) {
@@ -447,7 +738,7 @@ export class Inspector {
447
738
  }
448
739
 
449
740
  updateCalculation(fact, elr) {
450
- $('.calculations .tree').empty().append(this._calculationHTML(fact, elr));
741
+ $('.calculations .tree').empty().append(this._calculationHTML(fact));
451
742
  }
452
743
 
453
744
  createSummary() {
@@ -455,13 +746,40 @@ export class Inspector {
455
746
  this._populateFactSummary(summaryDom);
456
747
  this._populateTagSummary(summaryDom);
457
748
  this._populateFileSummary(summaryDom);
749
+ this._populateReportCreation(summaryDom);
458
750
  }
459
751
 
460
752
  _populateFactSummary(summaryDom) {
461
753
  const totalFacts = this.summary.totalFacts();
462
- $("<span></span>")
463
- .text(totalFacts)
464
- .appendTo(summaryDom.find(".total-facts-value"));
754
+ $(".total-facts-value", summaryDom)
755
+ .text(totalFacts)
756
+ .on("click", () => {
757
+ this.resetSearchFilters();
758
+ this.pushInspectorMode("search-mode", "summary-mode");
759
+ });
760
+
761
+ const hiddenFacts = this.summary.hiddenFacts();
762
+ $(".hidden-facts-value", summaryDom)
763
+ .text(hiddenFacts)
764
+ .on("click", () => {
765
+ this.resetSearchFilters({visibility: 'hidden'});
766
+ this.pushInspectorMode("search-mode", "summary-mode");
767
+ });
768
+
769
+ const mandatoryFacts = this.summary.mandatoryFacts();
770
+ if (!mandatoryFacts) {
771
+ $('#mandatory-facts-row').hide();
772
+ $('#mandatory-fact-filter-checkbox').hide();
773
+ } else {
774
+ $('#mandatory-facts-row').show();
775
+ $('#mandatory-fact-filter-checkbox').show();
776
+ $(".mandatory-facts-value", summaryDom)
777
+ .text(mandatoryFacts)
778
+ .on("click", () => {
779
+ this.resetSearchFilters({mandatoryFacts: true});
780
+ this.pushInspectorMode("search-mode", "summary-mode");
781
+ });
782
+ }
465
783
  }
466
784
 
467
785
  _populateTagSummary(summaryDom) {
@@ -500,7 +818,7 @@ export class Inspector {
500
818
  const sortedPrefixCounts = [...tagCounts.entries()].sort((a, b) => a[0].localeCompare(b[0]));
501
819
  for (const [prefix, counts] of sortedPrefixCounts) {
502
820
  const countRow = $("<tr></tr>").appendTo(summaryTagsTableBody);
503
- countRow.append($("<th></th>").attr("scope", "row").text(prefix));
821
+ countRow.append($("<th></th>").attr("scope", "row").text(this._reportSet.preferredPrefix(prefix)));
504
822
  insertTagCount(countRow, counts[PRIMARY_ITEMS_KEY], totalPrimaryItemTags);
505
823
  insertTagCount(countRow, counts[DIMENSIONS_KEY], totalDimensionTags);
506
824
  insertTagCount(countRow, counts[MEMBERS_KEY], totalMemberTags);
@@ -555,16 +873,31 @@ export class Inspector {
555
873
  }
556
874
  };
557
875
 
876
+ _populateReportCreation(summaryDom) {
877
+ const softwareCredits = this.summary.getSoftwareCredits();
878
+
879
+ const reportCreationContent = summaryDom.find(".report-creation");
880
+
881
+ if (softwareCredits.length > 0) {
882
+ const ul = reportCreationContent.find('ul');
883
+ for (const softwareCredit of softwareCredits) {
884
+ ul.append($("<li></li>").text(softwareCredit));
885
+ }
886
+ } else {
887
+ reportCreationContent.hide();
888
+ }
889
+ };
890
+
558
891
  createOutline() {
559
892
  if (this.outline.hasOutline()) {
560
893
  $('.outline .no-outline-overlay').hide();
561
894
  const container = $('<div class="fact-list"></div>').appendTo($('.outline .body'));
562
- for (const elr of this.outline.sortedSections()) {
563
- $('<div class="fact-list-item"></div>')
564
- .text(this._report.getRoleLabel(elr))
565
- .click(() => this.selectItem(this.outline.sections[elr].id))
566
- .dblclick(() => $('#inspector').removeClass("outline-mode"))
567
- .mousedown((e) => {
895
+ for (const group of this.outline.sortedSections()) {
896
+ $('<button class="fact-list-item"></button>')
897
+ .text(group.report.getRoleLabelOrURI(group.elr))
898
+ .on("click", () => this.selectItem(group.fact.vuid))
899
+ .on("dblclick", () => $('#inspector').removeClass("outline-mode"))
900
+ .on("mousedown", (e) => {
568
901
  // Prevent text selection by double click
569
902
  if (e.detail > 1) {
570
903
  e.preventDefault()
@@ -577,10 +910,10 @@ export class Inspector {
577
910
 
578
911
  updateOutline(cf) {
579
912
  $('.fact-groups').empty();
580
- for (const elr of this.outline.groupsForFact(cf)) {
581
- $('<div class="fact-list-item"></div>')
582
- .text(this._report.getRoleLabel(elr))
583
- .click(() => this.selectItem(this.outline.sections[elr].id))
913
+ for (const group of this.outline.groupsForFact(cf)) {
914
+ $('<button class="fact-list-item"></button>')
915
+ .text(cf.report.getRoleLabelOrURI(group.elr))
916
+ .on("click", () => this.selectItem(group.fact.vuid))
584
917
  .appendTo($('.fact-groups'));
585
918
  }
586
919
 
@@ -608,12 +941,12 @@ export class Inspector {
608
941
  const html = $("<ul></ul>");
609
942
  if (anchors.length > 0) {
610
943
  for (const c of anchors) {
611
- const otherFacts = this._report.getAlignedFacts(fact, { "c": c });
612
- const label = this._report.getLabel(c, "std", true);
944
+ const otherFacts = fact.report.getAlignedFacts(fact, { "c": c });
945
+ const labelLang = fact.report.getLabelAndLang(c, "std", true);
613
946
 
614
947
  $("<li></li>")
615
948
  .appendTo(html)
616
- .append(this.factLinkHTML(label, otherFacts));
949
+ .append(this.factLinkHTML(labelLang, otherFacts));
617
950
  }
618
951
  }
619
952
  else {
@@ -625,7 +958,7 @@ export class Inspector {
625
958
  }
626
959
 
627
960
  updateAnchoring(fact) {
628
- if (!this._report.usesAnchoring()) {
961
+ if (!this._reportSet.usesAnchoring()) {
629
962
  $('.anchoring').hide();
630
963
  }
631
964
  else {
@@ -642,8 +975,38 @@ export class Inspector {
642
975
 
643
976
  }
644
977
 
978
+ labelRoleSort([role1, roleLabel1, label1], [role2, roleLabel2, label2]) {
979
+ // Sort built-ins before others. Reverse so that -1 (not found) sorts
980
+ // after the last built-in.
981
+ const builtIn = ['std', 'doc'].reverse();
982
+ const p1 = builtIn.indexOf(role1);
983
+ const p2 = builtIn.indexOf(role2);
984
+
985
+ if (p1 != p2) {
986
+ return p2 - p1;
987
+ }
988
+
989
+ return roleLabel1.localeCompare(roleLabel2);
990
+ }
991
+
992
+ updateLabels(fact) {
993
+ const container = $("div.labels").empty();
994
+ const dl = $("<dl></dl>").appendTo(container);
995
+ for (const [role, roleLabel, label] of
996
+ Object.entries(fact.concept().labels())
997
+ .map(([role, label]) => [role, fact.report.getLabelRoleLabel(role), label])
998
+ .sort(this.labelRoleSort)) {
999
+ $("<dt></dt>")
1000
+ .text(roleLabel)
1001
+ .appendTo(dl);
1002
+ $("<dd></dd>")
1003
+ .text(label)
1004
+ .appendTo(dl);
1005
+ }
1006
+ return dl;
1007
+ }
1008
+
645
1009
  _referencesHTML(fact) {
646
- const c = fact.concept();
647
1010
  const a = new Accordian();
648
1011
  for (const [i, r] of fact.concept().references().entries()) {
649
1012
  const title = $("<span></span>").text(r[0].value);
@@ -665,47 +1028,95 @@ export class Inspector {
665
1028
  }
666
1029
 
667
1030
  _calculationHTML(fact, elr) {
668
- const calc = new Calculation(fact);
1031
+ const calc = new Calculation(fact, this._useCalc11);
669
1032
  if (!calc.hasCalculations()) {
670
1033
  return "";
671
1034
  }
672
1035
  const tableFacts = this._viewer.factsInSameTable(fact);
673
- if (!elr) {
674
- elr = calc.bestELRForFactSet(tableFacts);
675
- }
676
- const report = this._report;
677
- const inspector = this;
1036
+ const selectedELR = calc.bestELRForFactSet(tableFacts);
1037
+ const report = fact.report;
678
1038
  const a = new Accordian();
679
1039
 
680
- for (const [e, rolePrefix] of Object.entries(calc.elrs())) {
681
- const label = report.getRoleLabel(rolePrefix, inspector._viewerOptions);
682
-
683
- const rCalc = calc.resolvedCalculation(e);
1040
+ for (const rCalc of calc.resolvedCalculations()) {
1041
+ const label = report.getRoleLabelOrURI(rCalc.elr);
684
1042
  const calcBody = $('<div></div>');
685
- for (const [i, r] of rCalc.entries()) {
686
- const itemHTML = $("<div></div>")
1043
+ const calcTable = $('<table></table>')
1044
+ .addClass("calculation-table")
1045
+ .appendTo(calcBody);
1046
+
1047
+ for (const r of rCalc.rows) {
1048
+ const itemHTML = $("<tr></tr>")
687
1049
  .addClass("item")
688
- .append($("<span></span>").addClass("weight").text(r.weightSign + " "))
689
- .append($("<span></span>").addClass("concept-name").text(report.getLabelOrName(r.concept, "std")))
690
- .appendTo(calcBody);
1050
+ .append($("<td></td>").addClass("weight").text(r.weightSign))
1051
+ .append($("<td></td>").addClass("concept-name").text(r.concept.label()))
1052
+ .append($("<td></td>").addClass("value"))
1053
+ .appendTo(calcTable);
691
1054
 
692
- // r.facts is a map of fact IDs to Fact objects
693
- if (r.facts) {
1055
+ if (!r.facts.isEmpty()) {
694
1056
  itemHTML.addClass("calc-fact-link");
695
- itemHTML.data('ivids', Object.keys(r.facts));
696
- itemHTML.click(() => inspector.selectItem(Object.values(r.facts)[0].id));
697
- itemHTML.mouseenter(() => Object.values(r.facts).forEach(f => this._viewer.linkedHighlightFact(f)));
698
- itemHTML.mouseleave(() => Object.values(r.facts).forEach(f => this._viewer.clearLinkedHighlightFact(f)));
699
- Object.values(r.facts).forEach(f => this._viewer.highlightRelatedFact(f));
1057
+ itemHTML.find(".concept-name").contents().wrap($("<button></button>").addClass("inline-button"));
1058
+ itemHTML.data('ivids', r.facts.items().map(f => f.vuid));
1059
+ itemHTML.on("click", () => this.selectItem(r.facts.items()[0].vuid));
1060
+ itemHTML.on("mouseenter", () => r.facts.items().forEach(f => this._viewer.linkedHighlightFact(f)));
1061
+ itemHTML.on("mouseleave", () => r.facts.items().forEach(f => this._viewer.clearLinkedHighlightFact(f)));
1062
+ r.facts.items().forEach(f => this._viewer.highlightRelatedFact(f));
1063
+ itemHTML.find(".value").text(r.facts.mostPrecise().readableValue());
700
1064
  }
701
1065
  }
702
- $("<div></div>").addClass("item").addClass("total")
703
- .append($("<span></span>").addClass("weight"))
704
- .append($("<span></span>").addClass("concept-name").text(fact.getLabelOrName("std")))
1066
+ $("<tr></tr>").addClass("item").addClass("total")
1067
+ .append($("<td></td>").addClass("weight"))
1068
+ .append($("<td></td>").addClass("concept-name").text(fact.concept().label()))
1069
+ .append($("<td></td>").addClass("value").text(fact.readableValue()))
1070
+ .appendTo(calcTable);
1071
+
1072
+ const calcStatusIcon = $("<span></span>");
1073
+ const cardTitle = $("<span></span>")
1074
+ .append(calcStatusIcon)
1075
+ .append($("<span></span>").text(label));
1076
+ const calcStatusText = $("<span></span>");
1077
+ const calcDetailsLink = $("<button></button>")
1078
+ .addClass("calculation-details-link")
1079
+ .attr("title", i18next.t('factDetails.viewCalculationDetails'))
1080
+ .text("details")
1081
+ .on("click", (e) => {
1082
+ const dialog = new CalculationInspector();
1083
+ dialog.displayCalculation(rCalc);
1084
+ dialog.show();
1085
+ e.stopPropagation();
1086
+ })
1087
+ $("<p></p>")
1088
+ .append(calcStatusText)
1089
+ .append($("<span></span>").text(" ("))
1090
+ .append(calcDetailsLink)
1091
+ .append($("<span></span>").text(")"))
705
1092
  .appendTo(calcBody);
1093
+ if (rCalc.binds()) {
1094
+ if (rCalc.isConsistent()) {
1095
+ calcStatusIcon
1096
+ .addClass("consistent-flag")
1097
+ .attr("title", i18next.t('factDetails.calculationIsConsistent'))
1098
+ calcStatusText.text(i18next.t('factDetails.calculationIsConsistent'));
1099
+ }
1100
+ else {
1101
+ calcStatusIcon
1102
+ .addClass("inconsistent-flag")
1103
+ .attr("title", i18next.t('factDetails.calculationIsInconsistent'))
1104
+ calcStatusText.text(i18next.t('factDetails.calculationIsInconsistent'));
1105
+ }
1106
+ }
1107
+ else if (rCalc.unchecked()) {
1108
+ calcStatusIcon
1109
+ .addClass("unchecked-flag")
1110
+ .attr("title", i18next.t('factDetails.calculationUnchecked'))
1111
+ if (rCalc.uncheckedDueToVersionMismatch()) {
1112
+ calcStatusText.text(i18next.t('factDetails.calculationUncheckedIncorrectVersion'));
1113
+ }
1114
+ else {
1115
+ calcStatusText.text(i18next.t('factDetails.calculationUnchecked'));
1116
+ }
1117
+ }
706
1118
 
707
- a.addCard($("<span></span>").text(label), calcBody, e == elr);
708
-
1119
+ a.addCard(cardTitle, calcBody, rCalc.elr == selectedELR);
709
1120
  }
710
1121
  return a.contents();
711
1122
  }
@@ -714,12 +1125,12 @@ export class Inspector {
714
1125
  const html = $("<div></div>").addClass("fact-list");
715
1126
  for (const fn of fact.footnotes()) {
716
1127
  if (fn instanceof Footnote) {
717
- $("<div></div>")
1128
+ $("<button></button>")
718
1129
  .addClass("block-list-item")
719
1130
  .text(truncateLabel(fn.textContent(), 120))
720
- .mouseenter(() => this._viewer.linkedHighlightFact(fn))
721
- .mouseleave(() => this._viewer.clearLinkedHighlightFact(fn))
722
- .click(() => this.selectItem(fn.id))
1131
+ .on("mouseenter", () => this._viewer.linkedHighlightFact(fn))
1132
+ .on("mouseleave", () => this._viewer.clearLinkedHighlightFact(fn))
1133
+ .on("click", () => this.selectItem(fn.vuid))
723
1134
  .appendTo(html);
724
1135
  }
725
1136
  else if (fn instanceof Fact) {
@@ -760,14 +1171,15 @@ export class Inspector {
760
1171
  }
761
1172
  }
762
1173
 
763
- factLinkHTML(label, factList) {
764
- const html = $("<span></span>").text(label);
1174
+ factLinkHTML(labelLang, factList) {
1175
+ const html = $("<span></span>");
1176
+ this._setLabelWithLang(html, labelLang);
765
1177
  if (factList.length > 0) {
766
1178
  html
767
1179
  .addClass("fact-link")
768
- .click(() => this.selectItem(factList[0].id))
769
- .mouseenter(() => factList.forEach(f => this._viewer.linkedHighlightFact(f)))
770
- .mouseleave(() => factList.forEach(f => this._viewer.clearLinkedHighlightFact(f)));
1180
+ .on("click", () => this.selectItem(factList[0].vuid))
1181
+ .on("mouseenter", () => factList.forEach(f => this._viewer.linkedHighlightFact(f)))
1182
+ .on("mouseleave", () => factList.forEach(f => this._viewer.clearLinkedHighlightFact(f)));
771
1183
  }
772
1184
  return html;
773
1185
  }
@@ -775,7 +1187,7 @@ export class Inspector {
775
1187
  getPeriodIncrease(fact) {
776
1188
  let s = "";
777
1189
  if (fact.isNumeric()) {
778
- const otherFacts = this._report.getAlignedFacts(fact, {"p":null });
1190
+ const otherFacts = fact.report.getAlignedFacts(fact, {"p":null });
779
1191
  var mostRecent;
780
1192
  if (fact.periodTo()) {
781
1193
  for (const other of otherFacts) {
@@ -785,10 +1197,10 @@ export class Inspector {
785
1197
  }
786
1198
  }
787
1199
  if (mostRecent) {
788
- const allMostRecent = this._report.getAlignedFacts(mostRecent);
1200
+ const allMostRecent = fact.report.getAlignedFacts(mostRecent);
789
1201
  s = $("<span></span>")
790
1202
  .text(this.describeChange(mostRecent, fact))
791
- .append(this.factLinkHTML(mostRecent.periodString(), allMostRecent));
1203
+ .append(this.factLinkHTML({label: mostRecent.periodString()}, allMostRecent));
792
1204
 
793
1205
  }
794
1206
  else {
@@ -796,18 +1208,18 @@ export class Inspector {
796
1208
  }
797
1209
  }
798
1210
  else {
799
- s = $("<i>").text("n/a").attr("title", "non-numeric fact");
1211
+ s = $("<i>").text("n/a").attr("title", i18next.t('factDetails.nonNumericFact'));
800
1212
  }
801
1213
  $(".fact-properties tr.change td").html(s);
802
1214
 
803
1215
  }
804
1216
 
805
1217
  _updateValue(item, showAll, context) {
806
- const text = item.readableValue();
1218
+ const valueHTML = item.readableValueHTML();
807
1219
  const tr = $('tr.value', context);
808
- let v = text;
809
- if (!showAll) {
810
- const vv = wrapLabel(text, 120);
1220
+ const valueSpan = tr.find('td .value').empty();
1221
+ if (!(item instanceof Fact && item.isNumeric()) && !showAll) {
1222
+ const vv = wrapLabel(valueHTML.textContent, 120);
811
1223
  if (vv.length > 1) {
812
1224
  tr.addClass("truncated");
813
1225
  tr.find('.show-all')
@@ -817,10 +1229,11 @@ export class Inspector {
817
1229
  else {
818
1230
  tr.removeClass('truncated');
819
1231
  }
820
- v = vv[0];
1232
+ valueSpan.text(vv[0]);
821
1233
  }
822
1234
  else {
823
1235
  tr.removeClass('truncated');
1236
+ valueSpan.append(valueHTML);
824
1237
  }
825
1238
 
826
1239
  // Only enable text block viewer for escaped, text block facts. This
@@ -830,17 +1243,12 @@ export class Inspector {
830
1243
  tr
831
1244
  .addClass('text-block')
832
1245
  .find('.expand-text-block')
833
- .off().click(() => this.showTextBlock(item));
1246
+ .off().on("click", () => this.showTextBlock(item));
834
1247
  }
835
1248
  else {
836
1249
  tr.removeClass('text-block');
837
1250
  }
838
1251
 
839
- const valueSpan = tr.find('td .value').empty().text(v);
840
- if (item instanceof Fact && (item.isNil() || item.isInvalidIXValue())) {
841
- valueSpan.wrapInner("<i></i>");
842
- }
843
-
844
1252
  }
845
1253
 
846
1254
  showTextBlock(item) {
@@ -849,6 +1257,38 @@ export class Inspector {
849
1257
  tbd.show();
850
1258
  }
851
1259
 
1260
+ _updateDataType(fact, context) {
1261
+ const dt = fact.concept()?.dataType();
1262
+ if (dt !== undefined) {
1263
+ $('tr.datatype td', context).text(dt.label());
1264
+ }
1265
+ else {
1266
+ $('tr.datatype', context).hide();
1267
+ }
1268
+ }
1269
+
1270
+ _updateBalance(fact, context) {
1271
+ const b = fact.concept()?.balance();
1272
+ if (b !== undefined) {
1273
+ $('tr.balance td', context).text(b.label());
1274
+ }
1275
+ else {
1276
+ $('tr.balance', context).hide();
1277
+ }
1278
+ }
1279
+
1280
+ _updateConcept(fact, context) {
1281
+ $('tr.concept td', context)
1282
+ .find('.text')
1283
+ .text(fact.conceptDisplayName())
1284
+ .attr("title", fact.conceptDisplayName())
1285
+ .end()
1286
+ .find('.clipboard-copy')
1287
+ .data('cb-text', fact.conceptDisplayName())
1288
+ .end();
1289
+
1290
+ }
1291
+
852
1292
  _updateEntityIdentifier(fact, context) {
853
1293
  $('tr.entity-identifier td', context)
854
1294
  .empty()
@@ -863,6 +1303,20 @@ export class Inspector {
863
1303
  return html;
864
1304
  }
865
1305
 
1306
+ _setLabelWithLang(elt, labelLang) {
1307
+ elt.removeAttr("lang");
1308
+ if (labelLang.label !== undefined) {
1309
+ elt.text(labelLang.label);
1310
+ if (labelLang.lang !== undefined) {
1311
+ elt.attr("lang", labelLang.lang);
1312
+ }
1313
+ }
1314
+ else {
1315
+ elt.text("");
1316
+ }
1317
+ return elt;
1318
+ }
1319
+
866
1320
  /*
867
1321
  * Build an accordian containing a summary of all nested facts/footnotes
868
1322
  * corresponding to the current viewer selection.
@@ -872,7 +1326,7 @@ export class Inspector {
872
1326
 
873
1327
  // dissolveSingle => title not shown if only one item in accordian
874
1328
  const a = new Accordian({
875
- onSelect: (id) => this.switchItem(id),
1329
+ onSelect: (vuid) => this.switchItem(vuid),
876
1330
  alwaysOpen: true,
877
1331
  dissolveSingle: true,
878
1332
  });
@@ -883,26 +1337,22 @@ export class Inspector {
883
1337
  const title = fs.minimallyUniqueLabel(fact);
884
1338
  if (fact instanceof Fact) {
885
1339
  factHTML = $(require('../html/fact-details.html'));
886
- $('.std-label', factHTML).text(fact.getLabelOrName("std", true));
887
- $('.documentation', factHTML).text(fact.getLabel("doc") || "");
888
- $('tr.concept td', factHTML)
889
- .find('.text')
890
- .text(fact.conceptName())
891
- .attr("title", fact.conceptName())
892
- .end()
893
- .find('.clipboard-copy')
894
- .data('cb-text', fact.conceptName())
895
- .end();
1340
+ this._setLabelWithLang($('.std-label', factHTML), fact.getLabelOrNameAndLang("std", true));
1341
+ this._setLabelWithLang($('.documentation', factHTML), fact.getLabelAndLang("doc"));
1342
+ this._updateConcept(fact, factHTML);
896
1343
  $('tr.period td', factHTML)
897
1344
  .text(fact.periodString());
898
1345
  if (fact.isNumeric()) {
899
1346
  $('tr.period td', factHTML).append(
900
- $("<span></span>")
901
- .addClass("analyse")
1347
+ $("<button></button>")
1348
+ .addClass(["analyse", "inline-button"])
1349
+ .attr("title", i18next.t("inspector.showAnalysisChart"))
902
1350
  .text("")
903
- .click(() => this.analyseDimension(fact, ["p"]))
1351
+ .on('click', () => this.analyseDimension(fact, ["p"]))
904
1352
  );
905
1353
  }
1354
+ this._updateDataType(fact, factHTML);
1355
+ this._updateBalance(fact, factHTML);
906
1356
  this._updateEntityIdentifier(fact, factHTML);
907
1357
  this._updateValue(fact, false, factHTML);
908
1358
 
@@ -922,19 +1372,18 @@ export class Inspector {
922
1372
  $('#dimensions-label', factHTML).hide();
923
1373
  }
924
1374
  for (const aspect of taxonomyDefinedAspects) {
925
- const h = $('<div class="dimension"></div>')
926
- .text(aspect.label() || aspect.name())
1375
+ const h = this._setLabelWithLang($('<div class="dimension"></div>'), aspect.labelOrNameAndLang())
927
1376
  .appendTo($('#dimensions', factHTML));
928
1377
  if (fact.isNumeric()) {
929
1378
  h.append(
930
- $("<span></span>")
931
- .addClass("analyse")
1379
+ $("<button></button>")
1380
+ .addClass(["analyse", "inline-button"])
1381
+ .attr("title", i18next.t("inspector.showAnalysisChart"))
932
1382
  .text("")
933
1383
  .on("click", () => this.analyseDimension(fact, [aspect.name()]))
934
1384
  )
935
1385
  }
936
- const s = $('<div class="dimension-value"></div>')
937
- .text(aspect.valueLabel())
1386
+ const s = this._setLabelWithLang($('<div class="dimension-value"></div>'), aspect.valueLabelAndLang())
938
1387
  .appendTo(h);
939
1388
  if (aspect.isNil()) {
940
1389
  s.wrapInner("<i></i>");
@@ -948,8 +1397,8 @@ export class Inspector {
948
1397
  a.addCard(
949
1398
  title,
950
1399
  factHTML,
951
- fact.id == cf.id,
952
- fact.id
1400
+ fact.vuid == cf.vuid,
1401
+ fact.vuid
953
1402
  );
954
1403
  }
955
1404
  return a;
@@ -960,6 +1409,62 @@ export class Inspector {
960
1409
  chart.analyseDimension(fact, dimensions);
961
1410
  }
962
1411
 
1412
+ toggleTooltip(icon) {
1413
+ if ($("#tooltip").hasClass("show")) {
1414
+ this.hideTooltip();
1415
+ }
1416
+ else {
1417
+ this.showTooltip(icon);
1418
+ }
1419
+ }
1420
+
1421
+ showTooltip(icon, hoverShow) {
1422
+ icon.closest(".has-tooltip").attr("aria-describedby", "tooltip");
1423
+ $("#tooltip .tooltip-text").text(i18next.t(`tooltips:${icon.data("tooltip-name")}`));
1424
+ $("#tooltip").addClass(hoverShow ? "hover-show" : "show");
1425
+ const glossaryLink = icon.data("tooltip-glossary-link");
1426
+ if (glossaryLink) {
1427
+ $("#tooltip").addClass("with-glossary-link");
1428
+ const url = new URL(GLOSSARY_URL);
1429
+ if (typeof glossaryLink === 'string' && glossaryLink.startsWith("#")) {
1430
+ url.hash = glossaryLink;
1431
+ }
1432
+ $("#tooltip .glossary-link a").attr("href", url.href);
1433
+ }
1434
+ else {
1435
+ $("#tooltip").removeClass("with-glossary-link");
1436
+ }
1437
+ this.positionTooltip(icon);
1438
+ }
1439
+
1440
+ hideTooltip(hoverShow) {
1441
+ const t = $("#tooltip");
1442
+ t.removeClass(hoverShow ? "hover-show" : "show");
1443
+ if (!t.hasClass("hover-show") && !t.hasClass("show")) {
1444
+ $(".has-tooltip").removeAttr("aria-describedby");
1445
+ }
1446
+ }
1447
+
1448
+
1449
+ positionTooltip(e) {
1450
+ const iconPos = e.offset();
1451
+ const tooltipWidth = 300;
1452
+ const clientWidth = document.documentElement.clientWidth;
1453
+ const right = clientWidth - Math.min(clientWidth - 30, iconPos.left + tooltipWidth);
1454
+ const left = Math.min(clientWidth - tooltipWidth, iconPos.left);
1455
+
1456
+ $("#tooltip")
1457
+ .css("inset","")
1458
+ .css("position", "fixed")
1459
+ .css("left", left)
1460
+ .css("right", right)
1461
+ .css("top", iconPos.top + 30);
1462
+
1463
+ if ($("#tooltip").get(0).getBoundingClientRect().bottom > document.documentElement.clientHeight) {
1464
+ $("#tooltip").css("top","").css("bottom", 30);
1465
+ }
1466
+ }
1467
+
963
1468
  update() {
964
1469
  const cf = this._currentItem;
965
1470
  if (!cf) {
@@ -968,6 +1473,7 @@ export class Inspector {
968
1473
  }
969
1474
  else {
970
1475
  $('#inspector').removeClass('no-fact-selected').removeClass("hidden-fact").removeClass("html-hidden-fact");
1476
+ $('#inspector .tags').show();
971
1477
 
972
1478
  $('#inspector .fact-inspector')
973
1479
  .empty()
@@ -981,20 +1487,21 @@ export class Inspector {
981
1487
  this.updateFootnotes(cf);
982
1488
  this.updateAnchoring(cf);
983
1489
  $('div.references').empty().append(this._referencesHTML(cf));
1490
+ this.updateLabels(cf);
984
1491
  $('#inspector .search-results .fact-list-item').removeClass('selected');
985
- $('#inspector .search-results .fact-list-item').filter((i, e) => $(e).data('ivid') == cf.id).addClass('selected');
1492
+ $('#inspector .search-results .fact-list-item').filter((i, e) => $(e).data('ivid') == cf.vuid).addClass('selected');
986
1493
 
987
1494
  const duplicates = cf.duplicates();
988
1495
  let n = 0;
989
1496
  const ndup = duplicates.length;
990
1497
  for (var i = 0; i < ndup; i++) {
991
- if (cf.id == duplicates[i].id) {
1498
+ if (cf.vuid == duplicates[i].vuid) {
992
1499
  n = i;
993
1500
  }
994
1501
  }
995
1502
  $('.duplicates .text').text(i18next.t('factDetails.duplicatesCount', { current: n + 1, total: ndup}));
996
- $('.duplicates .prev').off().click(() => this.selectItem(duplicates[(n+ndup-1) % ndup].id));
997
- $('.duplicates .next').off().click(() => this.selectItem(duplicates[(n+1) % ndup].id));
1503
+ $('.duplicates .prev').off().on("click", () => { this.selectItem(duplicates[(n+ndup-1) % ndup].vuid); $('.duplicates .prev').get(0).focus(); });
1504
+ $('.duplicates .next').off().on("click", () => { this.selectItem(duplicates[(n+1) % ndup].vuid); $('.duplicates .next').get(0).focus(); });
998
1505
 
999
1506
  this.getPeriodIncrease(cf);
1000
1507
  if (cf.isHidden()) {
@@ -1004,10 +1511,19 @@ export class Inspector {
1004
1511
  $('#inspector').addClass('html-hidden-fact');
1005
1512
  }
1006
1513
 
1514
+ const target = cf.targetDocument();
1515
+ if (target !== null) {
1516
+ $('#inspector .target-document-tag').text(target).show();
1517
+ }
1518
+ else {
1519
+ $('#inspector .target-document-tag').hide();
1520
+ }
1521
+
1007
1522
  }
1008
1523
  else if (cf instanceof Footnote) {
1009
1524
  $('#inspector').addClass('footnote-mode');
1010
1525
  $('#inspector .footnote-details .footnote-facts').empty().append(this._footnoteFactsHTML(cf));
1526
+ $('#inspector .tags').hide();
1011
1527
  }
1012
1528
  $('.fact-details').localize();
1013
1529
  }
@@ -1024,18 +1540,24 @@ export class Inspector {
1024
1540
  *
1025
1541
  * If itemIdList is omitted, the currently selected item list is reset to just
1026
1542
  * the primary item.
1543
+ *
1544
+ * noInspectorReset selects the fact, but doesn't close
1545
+ * search/summary/outline mode in the inspector.
1027
1546
  */
1028
- selectItem(id, itemIdList, noScroll) {
1547
+ selectItem(vuid, itemIdList, noScroll, noInspectorReset) {
1029
1548
  if (itemIdList === undefined) {
1030
- this._currentItemList = [ this._report.getItemById(id) ];
1549
+ this._currentItemList = [ this._reportSet.getItemById(vuid) ];
1031
1550
  }
1032
1551
  else {
1033
1552
  this._currentItemList = [];
1034
1553
  for (const itemId of itemIdList) {
1035
- this._currentItemList.push(this._report.getItemById(itemId));
1554
+ this._currentItemList.push(this._reportSet.getItemById(itemId));
1036
1555
  }
1037
1556
  }
1038
- this.switchItem(id, noScroll);
1557
+ this.switchItem(vuid, noScroll);
1558
+ if (!noInspectorReset) {
1559
+ this.inspectorMode(undefined);
1560
+ }
1039
1561
  }
1040
1562
 
1041
1563
  /*
@@ -1046,13 +1568,13 @@ export class Inspector {
1046
1568
  *
1047
1569
  * For footnotes, we currently only support a single footnote being selected.
1048
1570
  */
1049
- switchItem(id, noScroll) {
1050
- if (id !== null) {
1051
- this._currentItem = this._report.getItemById(id);
1571
+ switchItem(vuid, noScroll) {
1572
+ if (vuid !== null) {
1573
+ this._currentItem = this._reportSet.getItemById(vuid);
1052
1574
  if (!noScroll) {
1053
- this._viewer.showItemById(id);
1575
+ this._viewer.showItemById(vuid);
1054
1576
  }
1055
- this._viewer.highlightItem(id);
1577
+ this._viewer.highlightItem(vuid);
1056
1578
  }
1057
1579
  else {
1058
1580
  this._currentItem = null;
@@ -1066,6 +1588,10 @@ export class Inspector {
1066
1588
  if (urlParams.has("lang")) {
1067
1589
  return [ urlParams.get("lang") ];
1068
1590
  }
1591
+ const localStorageAppLang = localStorage.getItem(STORAGE_APP_LANGUAGE);
1592
+ if (localStorageAppLang) {
1593
+ return [ localStorageAppLang ];
1594
+ }
1069
1595
  const langs = window.navigator.languages || [ window.navigator.language || window.navigator.userLanguage ] ;
1070
1596
  if (langs.length == 0 || !langs[0]) {
1071
1597
  return ["en"];
@@ -1074,7 +1600,7 @@ export class Inspector {
1074
1600
  }
1075
1601
 
1076
1602
  selectDefaultLanguage() {
1077
- const al = this._report.availableLanguages();
1603
+ const al = this._reportSet.availableLanguages();
1078
1604
  for (const pl of this.preferredLanguages()) {
1079
1605
  for (const l of al) {
1080
1606
  if (l.toLowerCase() == pl.toLowerCase()) {
@@ -1082,27 +1608,27 @@ export class Inspector {
1082
1608
  }
1083
1609
  }
1084
1610
  }
1085
- return this._report.availableLanguages()[0];
1611
+ return this._reportSet.availableLanguages()[0];
1086
1612
  }
1087
1613
 
1088
- setLanguage(lang) {
1614
+ setDocumentLanguage(lang) {
1089
1615
  this._viewerOptions.language = lang;
1090
1616
  }
1091
1617
 
1092
1618
  showValidationReport() {
1093
1619
  const vr = new ValidationReportDialog();
1094
- vr.displayErrors(this._report.data.validation);
1620
+ vr.displayErrors(this._reportSet.validation());
1095
1621
  vr.show();
1096
1622
  }
1097
1623
 
1098
1624
  setupValidationReportIcon() {
1099
- if (this._report.hasValidationErrors()) {
1625
+ if (this._reportSet.hasValidationErrors()) {
1100
1626
  $("#ixv .validation-warning").show().on("click", () => this.showValidationReport());
1101
1627
  }
1102
1628
  }
1103
1629
 
1104
1630
  showValidationWarning() {
1105
- if (this._report.hasValidationErrors()) {
1631
+ if (this._reportSet.hasValidationErrors()) {
1106
1632
  const message = $("<div></div>").append("<p>This report contains <b>XBRL validation errors</b>. These errors may prevent this document from opening correctly in other XBRL software.</p>");
1107
1633
  const mb = new MessageBox("Validation errors", message, "View Details", "Dismiss");
1108
1634
  mb.show(() => this.showValidationReport());