ixbrl-viewer 1.4.39__py3-none-any.whl → 1.4.40__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.

Potentially problematic release.


This version of ixbrl-viewer might be problematic. Click here for more details.

Files changed (95) hide show
  1. iXBRLViewerPlugin/__init__.py +25 -22
  2. iXBRLViewerPlugin/_version.py +2 -2
  3. iXBRLViewerPlugin/constants.py +86 -1
  4. iXBRLViewerPlugin/featureConfig.py +4 -1
  5. iXBRLViewerPlugin/iXBRLViewer.py +28 -14
  6. iXBRLViewerPlugin/viewer/dist/ixbrlviewer.js +1 -1
  7. iXBRLViewerPlugin/viewer/i18next-parser.config.js +1 -1
  8. iXBRLViewerPlugin/viewer/src/html/fact-details.html +69 -38
  9. iXBRLViewerPlugin/viewer/src/html/footer-logo.html +4 -0
  10. iXBRLViewerPlugin/viewer/src/html/footnote-details.html +1 -1
  11. iXBRLViewerPlugin/viewer/src/html/inspector.html +318 -211
  12. iXBRLViewerPlugin/viewer/src/i18n/cy/balancetypes.json +1 -0
  13. iXBRLViewerPlugin/viewer/src/i18n/cy/currencies.json +13 -0
  14. iXBRLViewerPlugin/viewer/src/i18n/cy/datatypes.json +9 -0
  15. iXBRLViewerPlugin/viewer/src/i18n/cy/labelroles.json +24 -0
  16. iXBRLViewerPlugin/viewer/src/i18n/cy/referenceparts.json +10 -0
  17. iXBRLViewerPlugin/viewer/src/i18n/cy/scale.json +16 -0
  18. iXBRLViewerPlugin/viewer/src/i18n/cy/tooltips.json +17 -0
  19. iXBRLViewerPlugin/viewer/src/i18n/cy/translation.json +179 -0
  20. iXBRLViewerPlugin/viewer/src/i18n/en/balancetypes.json +4 -0
  21. iXBRLViewerPlugin/viewer/src/i18n/en/datatypes.json +10 -0
  22. iXBRLViewerPlugin/viewer/src/i18n/en/labelroles.json +4 -0
  23. iXBRLViewerPlugin/viewer/src/i18n/en/scale.json +16 -0
  24. iXBRLViewerPlugin/viewer/src/i18n/en/tooltips.json +17 -0
  25. iXBRLViewerPlugin/viewer/src/i18n/en/translation.json +50 -25
  26. iXBRLViewerPlugin/viewer/src/i18n/es/balancetypes.json +4 -0
  27. iXBRLViewerPlugin/viewer/src/i18n/es/datatypes.json +10 -0
  28. iXBRLViewerPlugin/viewer/src/i18n/es/labelroles.json +24 -0
  29. iXBRLViewerPlugin/viewer/src/i18n/es/scale.json +16 -0
  30. iXBRLViewerPlugin/viewer/src/i18n/es/tooltips.json +17 -0
  31. iXBRLViewerPlugin/viewer/src/i18n/es/translation.json +66 -40
  32. iXBRLViewerPlugin/viewer/src/icons/dark-mode.svg +4 -0
  33. iXBRLViewerPlugin/viewer/src/img/arelle-dark.svg +179 -0
  34. iXBRLViewerPlugin/viewer/src/img/inline-viewer-dark.svg +59 -0
  35. iXBRLViewerPlugin/viewer/src/js/accordian.js +2 -1
  36. iXBRLViewerPlugin/viewer/src/js/aspect.js +18 -7
  37. iXBRLViewerPlugin/viewer/src/js/balance.js +14 -0
  38. iXBRLViewerPlugin/viewer/src/js/chart.js +10 -6
  39. iXBRLViewerPlugin/viewer/src/js/concept.js +28 -1
  40. iXBRLViewerPlugin/viewer/src/js/concept.test.js +23 -2
  41. iXBRLViewerPlugin/viewer/src/js/datatype.js +20 -0
  42. iXBRLViewerPlugin/viewer/src/js/datatype.test.js +62 -0
  43. iXBRLViewerPlugin/viewer/src/js/dialog.js +3 -1
  44. iXBRLViewerPlugin/viewer/src/js/fact.js +16 -0
  45. iXBRLViewerPlugin/viewer/src/js/fact.test.js +3 -0
  46. iXBRLViewerPlugin/viewer/src/js/index.js +11 -3
  47. iXBRLViewerPlugin/viewer/src/js/inspector.js +498 -120
  48. iXBRLViewerPlugin/viewer/src/js/inspector.test.js +1 -1
  49. iXBRLViewerPlugin/viewer/src/js/ixbrlviewer.js +128 -30
  50. iXBRLViewerPlugin/viewer/src/js/ixbrlviewer.test.js +133 -20
  51. iXBRLViewerPlugin/viewer/src/js/menu.js +21 -3
  52. iXBRLViewerPlugin/viewer/src/js/outline.js +2 -2
  53. iXBRLViewerPlugin/viewer/src/js/report.js +60 -8
  54. iXBRLViewerPlugin/viewer/src/js/report.test.js +51 -5
  55. iXBRLViewerPlugin/viewer/src/js/reportset.js +20 -0
  56. iXBRLViewerPlugin/viewer/src/js/reportset.test.js +3 -3
  57. iXBRLViewerPlugin/viewer/src/js/search.js +23 -2
  58. iXBRLViewerPlugin/viewer/src/js/search.test.js +2 -2
  59. iXBRLViewerPlugin/viewer/src/js/summary.js +14 -0
  60. iXBRLViewerPlugin/viewer/src/js/tableExport.js +2 -1
  61. iXBRLViewerPlugin/viewer/src/js/taxonomynamer.js +34 -0
  62. iXBRLViewerPlugin/viewer/src/js/taxonomynamer.test.js +32 -0
  63. iXBRLViewerPlugin/viewer/src/js/theme.js +36 -0
  64. iXBRLViewerPlugin/viewer/src/js/unit.js +17 -2
  65. iXBRLViewerPlugin/viewer/src/js/util.js +16 -16
  66. iXBRLViewerPlugin/viewer/src/js/viewer.js +13 -7
  67. iXBRLViewerPlugin/viewer/src/less/accordian.less +8 -4
  68. iXBRLViewerPlugin/viewer/src/less/block-list.less +12 -6
  69. iXBRLViewerPlugin/viewer/src/less/calculation-inspector.less +2 -2
  70. iXBRLViewerPlugin/viewer/src/less/chart.less +8 -5
  71. iXBRLViewerPlugin/viewer/src/less/colours-dark-mode.less +40 -0
  72. iXBRLViewerPlugin/viewer/src/less/colours.less +28 -21
  73. iXBRLViewerPlugin/viewer/src/less/common.less +1 -1
  74. iXBRLViewerPlugin/viewer/src/less/components.less +3 -3
  75. iXBRLViewerPlugin/viewer/src/less/core.less +2 -0
  76. iXBRLViewerPlugin/viewer/src/less/dialog.less +13 -10
  77. iXBRLViewerPlugin/viewer/src/less/form-controls.less +33 -11
  78. iXBRLViewerPlugin/viewer/src/less/inspector.less +538 -299
  79. iXBRLViewerPlugin/viewer/src/less/loader.less +2 -2
  80. iXBRLViewerPlugin/viewer/src/less/menu.less +33 -15
  81. iXBRLViewerPlugin/viewer/src/less/summary.less +16 -6
  82. iXBRLViewerPlugin/viewer/src/less/tabs.less +5 -5
  83. iXBRLViewerPlugin/viewer/src/less/text-mixins.less +2 -1
  84. iXBRLViewerPlugin/viewer/src/less/validation-report.less +1 -1
  85. iXBRLViewerPlugin/viewer/src/less/viewer.less +30 -18
  86. {ixbrl_viewer-1.4.39.dist-info → ixbrl_viewer-1.4.40.dist-info}/METADATA +33 -5
  87. {ixbrl_viewer-1.4.39.dist-info → ixbrl_viewer-1.4.40.dist-info}/RECORD +95 -66
  88. {ixbrl_viewer-1.4.39.dist-info → ixbrl_viewer-1.4.40.dist-info}/WHEEL +1 -1
  89. tests/puppeteer/framework/page_objects/doc_frame.js +1 -1
  90. tests/puppeteer/tests/fact_properties.test.js +4 -4
  91. tests/unit_tests/iXBRLViewerPlugin/test_iXBRLViewer.py +69 -28
  92. {ixbrl_viewer-1.4.39.dist-info → ixbrl_viewer-1.4.40.dist-info}/LICENSE +0 -0
  93. {ixbrl_viewer-1.4.39.dist-info → ixbrl_viewer-1.4.40.dist-info}/NOTICE +0 -0
  94. {ixbrl_viewer-1.4.39.dist-info → ixbrl_viewer-1.4.40.dist-info}/entry_points.txt +0 -0
  95. {ixbrl_viewer-1.4.39.dist-info → ixbrl_viewer-1.4.40.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,7 @@
3
3
  import $ from 'jquery'
4
4
  import i18next from 'i18next';
5
5
  import jqueryI18next from 'jquery-i18next';
6
- import { formatNumber, wrapLabel, truncateLabel, runGenerator, SHOW_FACT, HIGHLIGHT_COLORS, viewerUniqueId } from "./util.js";
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_HIGHLIGHT_FACTS, STORAGE_HOME_LINK_QUERY} from "./util.js";
7
7
  import { ReportSearch } from "./search.js";
8
8
  import { IXBRLChart } from './chart.js';
9
9
  import { ViewerOptions } from './viewerOptions.js';
@@ -20,8 +20,19 @@ import { Calculation } from "./calculation.js";
20
20
  import { CalculationInspector } from './calculationInspector.js';
21
21
  import { ReportSetOutline } from './outline.js';
22
22
  import { DIMENSIONS_KEY, DocumentSummary, MEMBERS_KEY, PRIMARY_ITEMS_KEY, TOTAL_KEY } from './summary.js';
23
+ import { toggleTheme } from './theme.js';
23
24
 
24
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
+ };
25
36
 
26
37
  export class Inspector {
27
38
  constructor(iv) {
@@ -32,24 +43,30 @@ export class Inspector {
32
43
  }
33
44
 
34
45
  i18nInit() {
46
+ const langs = ["en", "cy", "es"];
47
+ const bundles = [
48
+ "translation",
49
+ "referenceParts",
50
+ "currencies",
51
+ "dataTypes",
52
+ "labelRoles",
53
+ "scale",
54
+ "balanceTypes",
55
+ "tooltips"
56
+ ];
35
57
  return i18next.init({
36
58
  lng: this.preferredLanguages()[0],
59
+ reloadOnLanguageChange: true,
37
60
  // Do not apply translations that are present but with an empty string
38
61
  returnEmptyString: false,
39
62
  fallbackLng: 'en',
40
63
  debug: false,
41
- resources: {
42
- en: {
43
- translation: require('../i18n/en/translation.json'),
44
- referenceParts: require('../i18n/en/referenceparts.json'),
45
- currencies: require('../i18n/en/currencies.json')
46
- },
47
- es: {
48
- translation: require('../i18n/es/translation.json'),
49
- referenceParts: require('../i18n/es/referenceparts.json'),
50
- currencies: require('../i18n/es/currencies.json')
51
- }
52
- }
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
+ )
53
70
  }).then((t) => {
54
71
  jqueryI18next.init(i18next, $, {
55
72
  tName: 't', // --> appends $.t = i18next.t
@@ -71,7 +88,7 @@ export class Inspector {
71
88
  inspector._reportSet = reportSet;
72
89
  inspector.i18nInit().then((t) => {
73
90
 
74
- $(".collapsible-header").on("click", function () {
91
+ $(".collapsible-header button:first-of-type").on("click", function () {
75
92
  const d = $(this).closest(".collapsible-section");
76
93
  d.toggleClass("collapsed");
77
94
  if (d.hasClass("collapsed")) {
@@ -88,18 +105,10 @@ export class Inspector {
88
105
  }
89
106
  }
90
107
  });
91
- $("#inspector .controls .search-button").on("click", function () {
92
- $(this).closest("#inspector").removeClass(["summary-mode", "outline-mode"]).toggleClass("search-mode");
93
- });
94
- $("#inspector .controls .summary-button").on("click", function () {
95
- $(this).closest("#inspector").removeClass(["outline-mode", "search-mode"]).toggleClass("summary-mode");
96
- });
97
- $("#inspector .controls .outline-button").on("click", function () {
98
- $(this).closest("#inspector").removeClass(["summary-mode", "search-mode"]).toggleClass("outline-mode");
99
- });
100
- $("#inspector-head .back").on("click", function () {
101
- $(this).closest("#inspector").removeClass(["summary-mode", "outline-mode", "search-mode"]);
102
- });
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());
103
112
  $(".popup-trigger").on("mouseenter", function () {
104
113
  $(this).find(".popup-content").show()
105
114
  }).on("mouseleave", function () {
@@ -108,12 +117,17 @@ export class Inspector {
108
117
  $("#inspector").on("click", ".clipboard-copy", function () {
109
118
  navigator.clipboard.writeText($(this).data("cb-text"));
110
119
  });
120
+
121
+ inspector.initializeTooltips();
122
+
111
123
  inspector._toolbarMenu = new Menu($("#toolbar-highlight-menu"));
112
124
  inspector.buildToolbarHighlightMenu();
113
125
 
114
126
  inspector._optionsMenu = new Menu($("#display-options-menu"));
115
127
  inspector.buildDisplayOptionsMenu();
116
128
 
129
+ inspector.buildHomeLink()
130
+
117
131
  $("#ixv").localize();
118
132
 
119
133
  // Listen to messages posted to this window
@@ -125,23 +139,64 @@ export class Inspector {
125
139
  inspector.createOutline();
126
140
  inspector._iv.setProgress(i18next.t("inspector.initializing")).then(() => {
127
141
  inspector._search = new ReportSearch(reportSet);
128
- inspector.buildDisplayOptionsMenu();
129
- inspector.buildToolbarHighlightMenu();
130
- inspector.buildHighlightKey();
142
+ inspector.handleFactDeepLink();
143
+ inspector.rebuildViewer();
131
144
  inspector.setupValidationReportIcon();
132
145
  inspector.initializeViewer();
146
+ inspector.doInitialSelection();
133
147
  resolve();
134
148
  });
135
149
  });
136
150
  });
137
151
  }
138
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
+
139
190
  initializeViewer() {
140
191
  this._viewer.onSelect.add((vuid, eltSet, byClick) => this.selectItem(vuid, eltSet, byClick));
141
192
  this._viewer.onMouseEnter.add((id) => this.viewerMouseEnter(id));
142
193
  this._viewer.onMouseLeave.add(id => this.viewerMouseLeave(id));
143
194
  $('.ixbrl-next-tag').on("click", () => this._viewer.selectNextTag(this._currentItem));
144
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);
145
200
  }
146
201
 
147
202
  postLoadAsync() {
@@ -168,6 +223,12 @@ export class Inspector {
168
223
  }
169
224
  }
170
225
 
226
+ doInitialSelection() {
227
+ if (!this._currentItem && this._iv.isFeatureEnabled(FEATURE_SEARCH_ON_STARTUP)) {
228
+ this.inspectorMode("search-mode");
229
+ }
230
+ }
231
+
171
232
  handleMessage(event) {
172
233
  const jsonString = event.originalEvent.data;
173
234
  let data;
@@ -207,30 +268,108 @@ export class Inspector {
207
268
  buildDisplayOptionsMenu() {
208
269
  this._optionsMenu.reset();
209
270
  if (this._reportSet) {
210
- const dl = this.selectDefaultLanguage();
211
- const langs = this._reportSet.availableLanguages();
212
- const langNames = new Intl.DisplayNames(this.preferredLanguages(), { "type": "language" });
271
+ // Doc language
272
+ const defaultDocLang = this.selectDefaultLanguage();
273
+ const docLangs = this._reportSet.availableLanguages();
274
+ const docLangNames = new Intl.DisplayNames(this.preferredLanguages(), { "type": "language" });
213
275
 
276
+ this._optionsMenu.addLabel(i18next.t("menu.documentLanguage"));
214
277
  this._optionsMenu.addCheckboxGroup(
215
- langs,
216
- Object.fromEntries(langs.map((l) => [l, langNames.of(l)])),
217
- dl,
218
- (lang) => { this.setLanguage(lang); this.update() },
278
+ docLangs,
279
+ Object.fromEntries(docLangs.map((l) => [l, docLangNames.of(l)])),
280
+ defaultDocLang,
281
+ (lang) => { this.setDocumentLanguage(lang); this.update() },
219
282
  "select-language"
220
283
  );
221
- this.setLanguage(dl);
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
222
301
  if (this._reportSet.filingDocuments()) {
302
+ this._optionsMenu.addLabel(i18next.t("menu.actions"));
223
303
  this._optionsMenu.addDownloadButton("Download filing documents", this._reportSet.filingDocuments())
224
304
  }
305
+
306
+ // Options
307
+ this._optionsMenu.addLabel(i18next.t("menu.options"));
225
308
  this._optionsMenu.addCheckboxItem(i18next.t("calculation.useCalculations11"), (useCalc11) => this.setCalculationMode(useCalc11), "calculation-mode", "select-language", this._useCalc11);
226
309
  }
310
+ let helpLinks = {}
311
+ let guideLinkUrl = this._iv.getGuideLinkUrl();
312
+ if (guideLinkUrl) {
313
+ helpLinks[i18next.t("menu.userGuide")] = guideLinkUrl;
314
+ }
315
+ let supportLinkUrl = this._iv.getSupportLinkUrl();
316
+ if (supportLinkUrl) {
317
+ helpLinks[i18next.t("menu.reportAnIssue")] = supportLinkUrl;
318
+ }
319
+ let surveyLinkUrl = this._iv.getSurveyLinkUrl();
320
+ if (surveyLinkUrl) {
321
+ helpLinks[i18next.t("menu.survey")] = surveyLinkUrl;
322
+ }
323
+ if (Object.entries(helpLinks).length > 0) {
324
+ this._optionsMenu.addLabel(i18next.t("menu.help"));
325
+ for (const [label, value] of Object.entries(helpLinks)) {
326
+ this._optionsMenu.addLink(label, value);
327
+ }
328
+ }
227
329
  this._iv.callPluginMethod("extendDisplayOptionsMenu", this._optionsMenu);
228
330
  }
229
331
 
332
+ buildHomeLink() {
333
+ $('#top-bar #home-link').remove();
334
+ if (!this._iv.isStaticFeatureEnabled(FEATURE_HOME_LINK_URL)) {
335
+ return;
336
+ }
337
+ let homeLinkUrl = this._iv.getStaticFeatureValue(FEATURE_HOME_LINK_URL);
338
+ let homeLinkText;
339
+ if (this._iv.isStaticFeatureEnabled(FEATURE_HOME_LINK_LABEL)) {
340
+ homeLinkText = this._iv.getStaticFeatureValue(FEATURE_HOME_LINK_LABEL);
341
+ } else {
342
+ homeLinkText = i18next.t("toolbar.homePage");
343
+ }
344
+ const query = sessionStorage.getItem(STORAGE_HOME_LINK_QUERY);
345
+ if (query) {
346
+ if (!homeLinkUrl.includes("?")) {
347
+ homeLinkUrl += "?";
348
+ } else {
349
+ homeLinkUrl += "&";
350
+ }
351
+ homeLinkUrl += query;
352
+ }
353
+ const homeLink = $('<a></a>')
354
+ .attr('href', homeLinkUrl)
355
+ .attr('id', 'home-link')
356
+ .text(homeLinkText);
357
+ $('#top-bar').prepend(homeLink);
358
+ }
359
+
360
+
361
+ highlightTagsOnStartup() {
362
+ const pref = window.localStorage.getItem(STORAGE_HIGHLIGHT_FACTS);
363
+ if (pref !== null) {
364
+ return JSON.parse(pref);
365
+ }
366
+ return this._iv.isFeatureEnabled(FEATURE_HIGHLIGHT_FACTS_ON_STARTUP);
367
+ }
368
+
230
369
  buildToolbarHighlightMenu() {
231
370
  const iv = this._iv;
232
371
  this._toolbarMenu.reset();
233
- this._toolbarMenu.addCheckboxItem(i18next.t("toolbar.xbrlElements"), (checked) => this.highlightAllTags(checked), "highlight-tags", null, this._iv.options.highlightTagsOnStartup);
372
+ this._toolbarMenu.addCheckboxItem(i18next.t("toolbar.xbrlElements"), (checked, explicitClick) => this.highlightAllTags(checked, explicitClick), "highlight-tags", null, this.highlightTagsOnStartup())
234
373
  if (iv.isReviewModeEnabled()) {
235
374
  this._toolbarMenu.addCheckboxItem("Untagged Numbers", function (checked) {
236
375
  const body = iv.viewer.contents().find("body");
@@ -265,7 +404,7 @@ export class Inspector {
265
404
  "Untagged Dates",
266
405
  ]
267
406
  } else {
268
- key = this._reportSet.namespaceGroups();
407
+ key = this._reportSet.namespaceGroups().map(p => this._reportSet.preferredPrefix(p));
269
408
  }
270
409
  this._iv.callPluginMethod("extendHighlightKey", key);
271
410
 
@@ -278,6 +417,50 @@ export class Inspector {
278
417
  }
279
418
  }
280
419
 
420
+ changeApplicationLanguage(lang) {
421
+ i18next.changeLanguage(lang);
422
+ this.rebuildViewer();
423
+ }
424
+
425
+ rebuildViewer() {
426
+ $("#ixv").localize();
427
+ this.buildDisplayOptionsMenu();
428
+ this.buildHomeLink()
429
+ this.buildToolbarHighlightMenu();
430
+ this.buildHighlightKey();
431
+ this.update();
432
+ }
433
+
434
+ inspectorMode(mode, toggle) {
435
+ const allModes = ["summary-mode", "outline-mode", "search-mode"];
436
+ const i = $("#inspector").removeClass(allModes.filter(m => m !== mode));
437
+ if (mode === undefined) {
438
+ this._prevInspectorMode = undefined;
439
+ return;
440
+ }
441
+ if (toggle) {
442
+ i.toggleClass(mode);
443
+ }
444
+ else {
445
+ i.addClass(mode);
446
+ }
447
+ }
448
+
449
+ /*
450
+ * Controls where the "back" button takes you. We only set this when you
451
+ * follow a link that switches between modes, otherwise back just takes you
452
+ * back to the main inspector mode.
453
+ */
454
+ pushInspectorMode(newMode, oldMode) {
455
+ this._prevInspectorMode = oldMode;
456
+ this.inspectorMode(newMode);
457
+ }
458
+
459
+ popInspectorMode() {
460
+ this.inspectorMode(this._prevInspectorMode);
461
+ this._prevInspectorMode = undefined;
462
+ }
463
+
281
464
  setCalculationMode(useCalc11) {
282
465
  this._useCalc11 = useCalc11;
283
466
  if (this._currentItem instanceof Fact) {
@@ -285,14 +468,18 @@ export class Inspector {
285
468
  }
286
469
  }
287
470
 
288
- highlightAllTags(checked) {
471
+ highlightAllTags(checked, explicitClick) {
472
+ if (explicitClick) {
473
+ window.localStorage.setItem(STORAGE_HIGHLIGHT_FACTS, JSON.stringify(checked));
474
+ }
289
475
  this._viewer.highlightAllTags(checked, this._reportSet.namespaceGroups());
290
476
  }
291
477
 
292
478
  factListRow(f) {
293
- const row = $('<div class="fact-list-item"></div>')
294
- .on("click", () => this.selectItem(f.vuid))
295
- .on("dblclick", () => $('#inspector').removeClass("search-mode"))
479
+ const row = $('<button class="fact-list-item"></button>')
480
+ // soft focus - highlight the fact, but don't close the search results
481
+ .on("click", () => this.selectItem(f.vuid, undefined, undefined, true))
482
+ .on("dblclick", () => this.selectItem(f.vuid))
296
483
  .on("mousedown", (e) => {
297
484
  /* Prevents text selection via double click without
298
485
  * disabling click+drag text selection (which user-select:
@@ -305,27 +492,31 @@ export class Inspector {
305
492
  .on("mouseenter", () => this._viewer.linkedHighlightFact(f))
306
493
  .on("mouseleave", () => this._viewer.clearLinkedHighlightFact(f))
307
494
  .data('ivid', f.vuid);
308
- $('<div class="select-icon"></div>')
495
+ $('<button class="select-icon"></button>')
496
+ .attr("title", i18next.t("search.viewFact"))
309
497
  .on("click", () => {
310
498
  this.selectItem(f.vuid);
311
- $('#inspector').removeClass("search-mode");
312
499
  })
313
500
  .appendTo(row)
314
- $('<div class="title"></div>')
315
- .text(f.getLabelOrName("std"))
501
+ this._setLabelWithLang($('<div class="title"></div>'), f.getLabelOrNameAndLang("std"))
502
+ .appendTo(row);
503
+ const dt = f.concept().dataType();
504
+ if (dt !== undefined) {
505
+ $('<div class="datatype">')
506
+ .text(dt.label())
316
507
  .appendTo(row);
508
+ }
317
509
  $('<div class="dimension"></div>')
318
510
  .text(f.period().toString())
319
511
  .appendTo(row);
320
512
 
321
513
  for (const aspect of f.aspects()) {
322
514
  if (aspect.isTaxonomyDefined() && !aspect.isNil()) {
323
- $('<div class="dimension"></div>')
324
- .text(aspect.valueLabel())
515
+ this._setLabelWithLang($('<div class="dimension"></div>'), aspect.valueLabelAndLang())
325
516
  .appendTo(row);
326
517
  }
327
518
  }
328
- const tags = $("<div></div>").addClass("tags").appendTo(row);
519
+ const tags = $("<div></div>").addClass("block-list-item-tags").appendTo(row);
329
520
  if (f.targetDocument() !== null) {
330
521
  $('<div class="hidden"></div>')
331
522
  .text(f.targetDocument())
@@ -361,25 +552,39 @@ export class Inspector {
361
552
  searchSpec() {
362
553
  const spec = {};
363
554
  spec.searchString = $('#ixbrl-search').val();
364
- spec.showVisibleFacts = $('#search-visible-fact-filter').prop('checked');
365
- spec.showHiddenFacts = $('#search-hidden-fact-filter').prop('checked');
366
- spec.namespacesFilter = $('#search-filter-namespaces select').val();
367
- spec.unitsFilter = $('#search-filter-units select').val();
368
- spec.scalesFilter = $('#search-filter-scales select').val();
369
- spec.periodFilter = $('#search-filter-period select').val();
555
+ spec.visibilityFilter = $('#search-filter-visibility').val();
556
+ spec.showMandatoryFacts = $('#search-mandatory-fact-filter').prop('checked');
370
557
  spec.conceptTypeFilter = $('#search-filter-concept-type').val();
558
+ for (const [key, name] of Object.entries(SEARCH_FILTER_MULTISELECTS)) {
559
+ spec[key] = $(`#${name} select`).val();
560
+ }
561
+
562
+ const selectedDataTypes = this._reportSet.getUsedConceptDataTypes().filter(d => spec.dataTypesFilter.includes(d.dataType.name));
563
+ if (
564
+ (spec.conceptTypeFilter == 'numeric' && selectedDataTypes.some(dt => !dt.isNumeric)) ||
565
+ (spec.conceptTypeFilter == 'text' && selectedDataTypes.some(dt => dt.isNumeric))) {
566
+ $("#search-filter-datatypes .datatype-conflict-warning").show();
567
+ }
568
+ else {
569
+ $("#search-filter-datatypes .datatype-conflict-warning").hide();
570
+ }
371
571
  spec.factValueFilter = $('#search-filter-fact-value').val();
372
- spec.calculationsFilter = $('#search-filter-calculations select').val();
373
- spec.dimensionTypeFilter = $('#search-filter-dimension-type select').val();
374
- spec.targetDocumentFilter = $('#search-filter-target-document select').val();
375
572
  return spec;
376
573
  }
377
574
 
575
+ hasActiveSearchFilters(searchSpec) {
576
+ return Object.keys(SEARCH_FILTER_MULTISELECTS).some(k => searchSpec[k].length > 0) ||
577
+ searchSpec.visibilityFilter !== '*' ||
578
+ searchSpec.showMandatoryFacts ||
579
+ searchSpec.conceptTypeFilter !== "*" ||
580
+ searchSpec.factValueFilter !== "*" ;
581
+ }
582
+
378
583
  setupSearchControls(viewer) {
379
584
  const inspector = this;
380
585
  $('.search-controls input, .search-controls select').on("change", () => this.search());
381
- $(".search-controls div.filter-toggle").on("click", () => $(".search-controls").toggleClass('show-filters'));
382
- $(".search-controls .search-filters .reset").on("click", () => this.resetSearchFilters());
586
+ $(".search-controls button.filter-toggle").on("click", () => $(".search-controls").toggleClass('show-filters'));
587
+ $(".search-controls .reset").on("click", () => this.resetSearchFilters());
383
588
  $(".search-controls .search-filters .reset-multiselect").on("click", function () {
384
589
  $(this).siblings().children('select option:selected').prop('selected', false);
385
590
  inspector.search();
@@ -393,9 +598,20 @@ export class Inspector {
393
598
  for (const prefix of this._reportSet.getUsedConceptPrefixes()) {
394
599
  $("<option>")
395
600
  .attr("value", prefix)
396
- .text(`${prefix} (${this._reportSet.prefixMap()[prefix]})`)
601
+ .text(`${this._reportSet.preferredPrefix(prefix)} (${this._reportSet.prefixMap()[prefix]})`)
397
602
  .appendTo('#search-filter-namespaces select');
398
603
  }
604
+ if (this._reportSet.getUsedConceptDataTypes().length > 0) {
605
+ for (const dataType of this._reportSet.getUsedConceptDataTypes()) {
606
+ $("<option>")
607
+ .attr("value", dataType.dataType.name)
608
+ .text(dataType.dataType.label())
609
+ .appendTo('#search-filter-datatypes select');
610
+ }
611
+ }
612
+ else {
613
+ $('#search-filter-datatypes').hide();
614
+ }
399
615
  const targetDocuments = Array.from(this._reportSet.getTargetDocuments());
400
616
  if (targetDocuments.length == 1 && targetDocuments[0] == null) {
401
617
  $('#search-filter-target-document').hide();
@@ -438,18 +654,16 @@ export class Inspector {
438
654
  return scalesOptions;
439
655
  }
440
656
 
441
- resetSearchFilters() {
657
+ resetSearchFilters(defaults) {
658
+ defaults = defaults ?? {};
442
659
  $("#search-filter-period select option:selected").prop("selected", false);
660
+ $("#search-filter-visibility").val(defaults.visibility ?? "*");
443
661
  $("#search-filter-concept-type").val("*");
444
662
  $("#search-filter-fact-value").val("*");
445
- $("#search-filter-calculations select option:selected").prop("selected", false);
446
- $("#search-filter-dimension-type select option:selected").prop("selected", false);
447
- $("#search-hidden-fact-filter").prop("checked", true);
448
- $("#search-visible-fact-filter").prop("checked", true);
449
- $("#search-filter-namespaces select option:selected").prop("selected", false);
450
- $("#search-filter-target-document select option:selected").prop("selected", false);
451
- $("#search-filter-units select option:selected").prop("selected", false);
452
- $("#search-filter-scales select option:selected").prop("selected", false);
663
+ $("#search-mandatory-fact-filter").prop("checked", defaults.mandatoryFacts ?? false);
664
+ for (const name of Object.values(SEARCH_FILTER_MULTISELECTS)) {
665
+ $(`#${name} select option:selected`).prop("selected", false);
666
+ }
453
667
  this.search();
454
668
  }
455
669
 
@@ -460,14 +674,20 @@ export class Inspector {
460
674
  this.search();
461
675
  }
462
676
 
463
- search () {
677
+ search() {
464
678
  const spec = this.searchSpec();
679
+ if (this.hasActiveSearchFilters(spec)) {
680
+ $("#inspector .search-controls").addClass("active-filters");
681
+ }
682
+ else {
683
+ $("#inspector .search-controls").removeClass("active-filters");
684
+ }
465
685
  const results = this._search.search(spec);
466
686
  if (results === undefined) {
467
687
  return;
468
688
  }
469
689
  const container = $('#inspector .search-results .results');
470
- $('div', container).remove();
690
+ container.empty();
471
691
  this._viewer.clearRelatedHighlighting();
472
692
  const overlay = $('#inspector .search-results .search-overlay');
473
693
  if (results.length > 0) {
@@ -479,18 +699,14 @@ export class Inspector {
479
699
  $(".text", overlay).text(i18next.t("search.tryAgainDifferentKeywords"));
480
700
  overlay.show();
481
701
  }
482
- $("#matching-facts-count").text(results.length);
702
+ $("#matching-facts-summary").text(i18next.t("search.matchingFactsSummary", {nMatches: results.length, nTotal: this._reportSet.facts().length}));
483
703
  /* Don't highlight search results if there's no search string */
484
704
  if (spec.searchString != "") {
485
705
  this._viewer.highlightRelatedFacts(results.map(r => r.fact));
486
706
  }
487
- this.updateMultiSelectSubheader('search-filter-scales');
488
- this.updateMultiSelectSubheader('search-filter-units');
489
- this.updateMultiSelectSubheader('search-filter-namespaces');
490
- this.updateMultiSelectSubheader('search-filter-target-document');
491
- this.updateMultiSelectSubheader('search-filter-dimension-type');
492
- this.updateMultiSelectSubheader('search-filter-calculations');
493
- this.updateMultiSelectSubheader('search-filter-period');
707
+ for (const name of Object.values(SEARCH_FILTER_MULTISELECTS)) {
708
+ this.updateMultiSelectSubheader(name);
709
+ }
494
710
  }
495
711
 
496
712
  updateMultiSelectSubheader(id) {
@@ -521,9 +737,35 @@ export class Inspector {
521
737
 
522
738
  _populateFactSummary(summaryDom) {
523
739
  const totalFacts = this.summary.totalFacts();
524
- $("<span></span>")
525
- .text(totalFacts)
526
- .appendTo(summaryDom.find(".total-facts-value"));
740
+ $(".total-facts-value", summaryDom)
741
+ .text(totalFacts)
742
+ .on("click", () => {
743
+ this.resetSearchFilters();
744
+ this.pushInspectorMode("search-mode", "summary-mode");
745
+ });
746
+
747
+ const hiddenFacts = this.summary.hiddenFacts();
748
+ $(".hidden-facts-value", summaryDom)
749
+ .text(hiddenFacts)
750
+ .on("click", () => {
751
+ this.resetSearchFilters({visibility: 'hidden'});
752
+ this.pushInspectorMode("search-mode", "summary-mode");
753
+ });
754
+
755
+ const mandatoryFacts = this.summary.mandatoryFacts();
756
+ if (!mandatoryFacts) {
757
+ $('#mandatory-facts-row').hide();
758
+ $('#mandatory-fact-filter-checkbox').hide();
759
+ } else {
760
+ $('#mandatory-facts-row').show();
761
+ $('#mandatory-fact-filter-checkbox').show();
762
+ $(".mandatory-facts-value", summaryDom)
763
+ .text(mandatoryFacts)
764
+ .on("click", () => {
765
+ this.resetSearchFilters({mandatoryFacts: true});
766
+ this.pushInspectorMode("search-mode", "summary-mode");
767
+ });
768
+ }
527
769
  }
528
770
 
529
771
  _populateTagSummary(summaryDom) {
@@ -637,8 +879,8 @@ export class Inspector {
637
879
  $('.outline .no-outline-overlay').hide();
638
880
  const container = $('<div class="fact-list"></div>').appendTo($('.outline .body'));
639
881
  for (const group of this.outline.sortedSections()) {
640
- $('<div class="fact-list-item"></div>')
641
- .text(group.report.getRoleLabel(group.elr))
882
+ $('<button class="fact-list-item"></button>')
883
+ .text(group.report.getRoleLabelOrURI(group.elr))
642
884
  .on("click", () => this.selectItem(group.fact.vuid))
643
885
  .on("dblclick", () => $('#inspector').removeClass("outline-mode"))
644
886
  .on("mousedown", (e) => {
@@ -655,8 +897,8 @@ export class Inspector {
655
897
  updateOutline(cf) {
656
898
  $('.fact-groups').empty();
657
899
  for (const group of this.outline.groupsForFact(cf)) {
658
- $('<div class="fact-list-item"></div>')
659
- .text(cf.report.getRoleLabel(group.elr))
900
+ $('<button class="fact-list-item"></button>')
901
+ .text(cf.report.getRoleLabelOrURI(group.elr))
660
902
  .on("click", () => this.selectItem(group.fact.vuid))
661
903
  .appendTo($('.fact-groups'));
662
904
  }
@@ -686,11 +928,11 @@ export class Inspector {
686
928
  if (anchors.length > 0) {
687
929
  for (const c of anchors) {
688
930
  const otherFacts = fact.report.getAlignedFacts(fact, { "c": c });
689
- const label = fact.report.getLabel(c, "std", true);
931
+ const labelLang = fact.report.getLabelAndLang(c, "std", true);
690
932
 
691
933
  $("<li></li>")
692
934
  .appendTo(html)
693
- .append(this.factLinkHTML(label, otherFacts));
935
+ .append(this.factLinkHTML(labelLang, otherFacts));
694
936
  }
695
937
  }
696
938
  else {
@@ -719,6 +961,37 @@ export class Inspector {
719
961
 
720
962
  }
721
963
 
964
+ labelRoleSort([role1, roleLabel1, label1], [role2, roleLabel2, label2]) {
965
+ // Sort built-ins before others. Reverse so that -1 (not found) sorts
966
+ // after the last built-in.
967
+ const builtIn = ['std', 'doc'].reverse();
968
+ const p1 = builtIn.indexOf(role1);
969
+ const p2 = builtIn.indexOf(role2);
970
+
971
+ if (p1 != p2) {
972
+ return p2 - p1;
973
+ }
974
+
975
+ return roleLabel1.localeCompare(roleLabel2);
976
+ }
977
+
978
+ updateLabels(fact) {
979
+ const container = $("div.labels").empty();
980
+ const dl = $("<dl></dl>").appendTo(container);
981
+ for (const [role, roleLabel, label] of
982
+ Object.entries(fact.concept().labels())
983
+ .map(([role, label]) => [role, fact.report.getLabelRoleLabel(role), label])
984
+ .sort(this.labelRoleSort)) {
985
+ $("<dt></dt>")
986
+ .text(roleLabel)
987
+ .appendTo(dl);
988
+ $("<dd></dd>")
989
+ .text(label)
990
+ .appendTo(dl);
991
+ }
992
+ return dl;
993
+ }
994
+
722
995
  _referencesHTML(fact) {
723
996
  const a = new Accordian();
724
997
  for (const [i, r] of fact.concept().references().entries()) {
@@ -751,7 +1024,7 @@ export class Inspector {
751
1024
  const a = new Accordian();
752
1025
 
753
1026
  for (const rCalc of calc.resolvedCalculations()) {
754
- const label = report.getRoleLabel(rCalc.elr);
1027
+ const label = report.getRoleLabelOrURI(rCalc.elr);
755
1028
  const calcBody = $('<div></div>');
756
1029
  const calcTable = $('<table></table>')
757
1030
  .addClass("calculation-table")
@@ -760,14 +1033,14 @@ export class Inspector {
760
1033
  for (const r of rCalc.rows) {
761
1034
  const itemHTML = $("<tr></tr>")
762
1035
  .addClass("item")
763
- .append($("<td></td>").addClass("weight").text(r.weightSign + " "))
1036
+ .append($("<td></td>").addClass("weight").text(r.weightSign))
764
1037
  .append($("<td></td>").addClass("concept-name").text(r.concept.label()))
765
1038
  .append($("<td></td>").addClass("value"))
766
1039
  .appendTo(calcTable);
767
1040
 
768
1041
  if (!r.facts.isEmpty()) {
769
1042
  itemHTML.addClass("calc-fact-link");
770
- itemHTML.addClass("calc-fact-link");
1043
+ itemHTML.find(".concept-name").contents().wrap($("<button></button>").addClass("inline-button"));
771
1044
  itemHTML.data('ivids', r.facts.items().map(f => f.vuid));
772
1045
  itemHTML.on("click", () => this.selectItem(r.facts.items()[0].vuid));
773
1046
  itemHTML.on("mouseenter", () => r.facts.items().forEach(f => this._viewer.linkedHighlightFact(f)));
@@ -787,7 +1060,7 @@ export class Inspector {
787
1060
  .append(calcStatusIcon)
788
1061
  .append($("<span></span>").text(label));
789
1062
  const calcStatusText = $("<span></span>");
790
- const calcDetailsLink = $("<span></span>")
1063
+ const calcDetailsLink = $("<button></button>")
791
1064
  .addClass("calculation-details-link")
792
1065
  .attr("title", i18next.t('factDetails.viewCalculationDetails'))
793
1066
  .text("details")
@@ -838,7 +1111,7 @@ export class Inspector {
838
1111
  const html = $("<div></div>").addClass("fact-list");
839
1112
  for (const fn of fact.footnotes()) {
840
1113
  if (fn instanceof Footnote) {
841
- $("<div></div>")
1114
+ $("<button></button>")
842
1115
  .addClass("block-list-item")
843
1116
  .text(truncateLabel(fn.textContent(), 120))
844
1117
  .on("mouseenter", () => this._viewer.linkedHighlightFact(fn))
@@ -884,8 +1157,9 @@ export class Inspector {
884
1157
  }
885
1158
  }
886
1159
 
887
- factLinkHTML(label, factList) {
888
- const html = $("<span></span>").text(label);
1160
+ factLinkHTML(labelLang, factList) {
1161
+ const html = $("<span></span>");
1162
+ this._setLabelWithLang(html, labelLang);
889
1163
  if (factList.length > 0) {
890
1164
  html
891
1165
  .addClass("fact-link")
@@ -912,7 +1186,7 @@ export class Inspector {
912
1186
  const allMostRecent = fact.report.getAlignedFacts(mostRecent);
913
1187
  s = $("<span></span>")
914
1188
  .text(this.describeChange(mostRecent, fact))
915
- .append(this.factLinkHTML(mostRecent.periodString(), allMostRecent));
1189
+ .append(this.factLinkHTML({label: mostRecent.periodString()}, allMostRecent));
916
1190
 
917
1191
  }
918
1192
  else {
@@ -973,6 +1247,38 @@ export class Inspector {
973
1247
  tbd.show();
974
1248
  }
975
1249
 
1250
+ _updateDataType(fact, context) {
1251
+ const dt = fact.concept()?.dataType();
1252
+ if (dt !== undefined) {
1253
+ $('tr.datatype td', context).text(dt.label());
1254
+ }
1255
+ else {
1256
+ $('tr.datatype', context).hide();
1257
+ }
1258
+ }
1259
+
1260
+ _updateBalance(fact, context) {
1261
+ const b = fact.concept()?.balance();
1262
+ if (b !== undefined) {
1263
+ $('tr.balance td', context).text(b.label());
1264
+ }
1265
+ else {
1266
+ $('tr.balance', context).hide();
1267
+ }
1268
+ }
1269
+
1270
+ _updateConcept(fact, context) {
1271
+ $('tr.concept td', context)
1272
+ .find('.text')
1273
+ .text(fact.conceptDisplayName())
1274
+ .attr("title", fact.conceptDisplayName())
1275
+ .end()
1276
+ .find('.clipboard-copy')
1277
+ .data('cb-text', fact.conceptDisplayName())
1278
+ .end();
1279
+
1280
+ }
1281
+
976
1282
  _updateEntityIdentifier(fact, context) {
977
1283
  $('tr.entity-identifier td', context)
978
1284
  .empty()
@@ -987,6 +1293,20 @@ export class Inspector {
987
1293
  return html;
988
1294
  }
989
1295
 
1296
+ _setLabelWithLang(elt, labelLang) {
1297
+ elt.removeAttr("lang");
1298
+ if (labelLang.label !== undefined) {
1299
+ elt.text(labelLang.label);
1300
+ if (labelLang.lang !== undefined) {
1301
+ elt.attr("lang", labelLang.lang);
1302
+ }
1303
+ }
1304
+ else {
1305
+ elt.text("");
1306
+ }
1307
+ return elt;
1308
+ }
1309
+
990
1310
  /*
991
1311
  * Build an accordian containing a summary of all nested facts/footnotes
992
1312
  * corresponding to the current viewer selection.
@@ -1007,26 +1327,22 @@ export class Inspector {
1007
1327
  const title = fs.minimallyUniqueLabel(fact);
1008
1328
  if (fact instanceof Fact) {
1009
1329
  factHTML = $(require('../html/fact-details.html'));
1010
- $('.std-label', factHTML).text(fact.getLabelOrName("std", true));
1011
- $('.documentation', factHTML).text(fact.getLabel("doc") || "");
1012
- $('tr.concept td', factHTML)
1013
- .find('.text')
1014
- .text(fact.conceptName())
1015
- .attr("title", fact.conceptName())
1016
- .end()
1017
- .find('.clipboard-copy')
1018
- .data('cb-text', fact.conceptName())
1019
- .end();
1330
+ this._setLabelWithLang($('.std-label', factHTML), fact.getLabelOrNameAndLang("std", true));
1331
+ this._setLabelWithLang($('.documentation', factHTML), fact.getLabelAndLang("doc"));
1332
+ this._updateConcept(fact, factHTML);
1020
1333
  $('tr.period td', factHTML)
1021
1334
  .text(fact.periodString());
1022
1335
  if (fact.isNumeric()) {
1023
1336
  $('tr.period td', factHTML).append(
1024
- $("<span></span>")
1025
- .addClass("analyse")
1337
+ $("<button></button>")
1338
+ .addClass(["analyse", "inline-button"])
1339
+ .attr("title", i18next.t("inspector.showAnalysisChart"))
1026
1340
  .text("")
1027
1341
  .on('click', () => this.analyseDimension(fact, ["p"]))
1028
1342
  );
1029
1343
  }
1344
+ this._updateDataType(fact, factHTML);
1345
+ this._updateBalance(fact, factHTML);
1030
1346
  this._updateEntityIdentifier(fact, factHTML);
1031
1347
  this._updateValue(fact, false, factHTML);
1032
1348
 
@@ -1046,19 +1362,18 @@ export class Inspector {
1046
1362
  $('#dimensions-label', factHTML).hide();
1047
1363
  }
1048
1364
  for (const aspect of taxonomyDefinedAspects) {
1049
- const h = $('<div class="dimension"></div>')
1050
- .text(aspect.label() || aspect.name())
1365
+ const h = this._setLabelWithLang($('<div class="dimension"></div>'), aspect.labelOrNameAndLang())
1051
1366
  .appendTo($('#dimensions', factHTML));
1052
1367
  if (fact.isNumeric()) {
1053
1368
  h.append(
1054
- $("<span></span>")
1055
- .addClass("analyse")
1369
+ $("<button></button>")
1370
+ .addClass(["analyse", "inline-button"])
1371
+ .attr("title", i18next.t("inspector.showAnalysisChart"))
1056
1372
  .text("")
1057
1373
  .on("click", () => this.analyseDimension(fact, [aspect.name()]))
1058
1374
  )
1059
1375
  }
1060
- const s = $('<div class="dimension-value"></div>')
1061
- .text(aspect.valueLabel())
1376
+ const s = this._setLabelWithLang($('<div class="dimension-value"></div>'), aspect.valueLabelAndLang())
1062
1377
  .appendTo(h);
1063
1378
  if (aspect.isNil()) {
1064
1379
  s.wrapInner("<i></i>");
@@ -1084,6 +1399,62 @@ export class Inspector {
1084
1399
  chart.analyseDimension(fact, dimensions);
1085
1400
  }
1086
1401
 
1402
+ toggleTooltip(icon) {
1403
+ if ($("#tooltip").hasClass("show")) {
1404
+ this.hideTooltip();
1405
+ }
1406
+ else {
1407
+ this.showTooltip(icon);
1408
+ }
1409
+ }
1410
+
1411
+ showTooltip(icon, hoverShow) {
1412
+ icon.closest(".has-tooltip").attr("aria-describedby", "tooltip");
1413
+ $("#tooltip .tooltip-text").text(i18next.t(`tooltips:${icon.data("tooltip-name")}`));
1414
+ $("#tooltip").addClass(hoverShow ? "hover-show" : "show");
1415
+ const glossaryLink = icon.data("tooltip-glossary-link");
1416
+ if (glossaryLink) {
1417
+ $("#tooltip").addClass("with-glossary-link");
1418
+ const url = new URL(GLOSSARY_URL);
1419
+ if (typeof glossaryLink === 'string' && glossaryLink.startsWith("#")) {
1420
+ url.hash = glossaryLink;
1421
+ }
1422
+ $("#tooltip .glossary-link a").attr("href", url.href);
1423
+ }
1424
+ else {
1425
+ $("#tooltip").removeClass("with-glossary-link");
1426
+ }
1427
+ this.positionTooltip(icon);
1428
+ }
1429
+
1430
+ hideTooltip(hoverShow) {
1431
+ const t = $("#tooltip");
1432
+ t.removeClass(hoverShow ? "hover-show" : "show");
1433
+ if (!t.hasClass("hover-show") && !t.hasClass("show")) {
1434
+ $(".has-tooltip").removeAttr("aria-describedby");
1435
+ }
1436
+ }
1437
+
1438
+
1439
+ positionTooltip(e) {
1440
+ const iconPos = e.offset();
1441
+ const tooltipWidth = 300;
1442
+ const clientWidth = document.documentElement.clientWidth;
1443
+ const right = clientWidth - Math.min(clientWidth - 30, iconPos.left + tooltipWidth);
1444
+ const left = Math.min(clientWidth - tooltipWidth, iconPos.left);
1445
+
1446
+ $("#tooltip")
1447
+ .css("inset","")
1448
+ .css("position", "fixed")
1449
+ .css("left", left)
1450
+ .css("right", right)
1451
+ .css("top", iconPos.top + 30);
1452
+
1453
+ if ($("#tooltip").get(0).getBoundingClientRect().bottom > document.documentElement.clientHeight) {
1454
+ $("#tooltip").css("top","").css("bottom", 30);
1455
+ }
1456
+ }
1457
+
1087
1458
  update() {
1088
1459
  const cf = this._currentItem;
1089
1460
  if (!cf) {
@@ -1105,6 +1476,7 @@ export class Inspector {
1105
1476
  this.updateFootnotes(cf);
1106
1477
  this.updateAnchoring(cf);
1107
1478
  $('div.references').empty().append(this._referencesHTML(cf));
1479
+ this.updateLabels(cf);
1108
1480
  $('#inspector .search-results .fact-list-item').removeClass('selected');
1109
1481
  $('#inspector .search-results .fact-list-item').filter((i, e) => $(e).data('ivid') == cf.vuid).addClass('selected');
1110
1482
 
@@ -1117,8 +1489,8 @@ export class Inspector {
1117
1489
  }
1118
1490
  }
1119
1491
  $('.duplicates .text').text(i18next.t('factDetails.duplicatesCount', { current: n + 1, total: ndup}));
1120
- $('.duplicates .prev').off().on("click", () => this.selectItem(duplicates[(n+ndup-1) % ndup].vuid));
1121
- $('.duplicates .next').off().on("click", () => this.selectItem(duplicates[(n+1) % ndup].vuid));
1492
+ $('.duplicates .prev').off().on("click", () => { this.selectItem(duplicates[(n+ndup-1) % ndup].vuid); $('.duplicates .prev').get(0).focus(); });
1493
+ $('.duplicates .next').off().on("click", () => { this.selectItem(duplicates[(n+1) % ndup].vuid); $('.duplicates .next').get(0).focus(); });
1122
1494
 
1123
1495
  this.getPeriodIncrease(cf);
1124
1496
  if (cf.isHidden()) {
@@ -1156,8 +1528,11 @@ export class Inspector {
1156
1528
  *
1157
1529
  * If itemIdList is omitted, the currently selected item list is reset to just
1158
1530
  * the primary item.
1531
+ *
1532
+ * noInspectorReset selects the fact, but doesn't close
1533
+ * search/summary/outline mode in the inspector.
1159
1534
  */
1160
- selectItem(vuid, itemIdList, noScroll) {
1535
+ selectItem(vuid, itemIdList, noScroll, noInspectorReset) {
1161
1536
  if (itemIdList === undefined) {
1162
1537
  this._currentItemList = [ this._reportSet.getItemById(vuid) ];
1163
1538
  }
@@ -1168,6 +1543,9 @@ export class Inspector {
1168
1543
  }
1169
1544
  }
1170
1545
  this.switchItem(vuid, noScroll);
1546
+ if (!noInspectorReset) {
1547
+ this.inspectorMode(undefined);
1548
+ }
1171
1549
  }
1172
1550
 
1173
1551
  /*
@@ -1217,7 +1595,7 @@ export class Inspector {
1217
1595
  return this._reportSet.availableLanguages()[0];
1218
1596
  }
1219
1597
 
1220
- setLanguage(lang) {
1598
+ setDocumentLanguage(lang) {
1221
1599
  this._viewerOptions.language = lang;
1222
1600
  }
1223
1601