@robosystems/report-components 0.1.4 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,33 +1,7 @@
1
- import { BLOCK_ORDER, BLOCK_TITLES, humanize, qname } from './chunk-JTTICHPF.js';
2
- export { BLOCK_ORDER, BLOCK_TITLES, NS, cypherAdapter, humanize, jsonldFileAdapter, parseJsonld, parseStore, qname } from './chunk-JTTICHPF.js';
3
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
- import { useMemo, useState } from 'react';
5
-
6
- // src/format.ts
7
- function formatMoney(value, opts = {}) {
8
- if (value === null || value === void 0 || Number.isNaN(value)) return "\u2014";
9
- const symbol = opts.currencySymbol ?? "$";
10
- const abs = Math.abs(value).toLocaleString("en-US", {
11
- minimumFractionDigits: 2,
12
- maximumFractionDigits: 2
13
- });
14
- return value < 0 ? `${symbol}(${abs})` : `${symbol}${abs}`;
15
- }
16
- var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
17
- function formatDate(iso) {
18
- if (!iso) return "";
19
- const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso);
20
- if (!m) return iso;
21
- const [, year, month, day] = m;
22
- const idx = Number(month) - 1;
23
- return `${MONTHS[idx] ?? month} ${Number(day)}, ${year}`;
24
- }
25
- function formatPeriod(period) {
26
- if (period.type === "instant") {
27
- return `As of ${formatDate(period.instant ?? period.end)}`;
28
- }
29
- return `${formatDate(period.startDate)} \u2013 ${formatDate(period.endDate ?? period.end)}`;
30
- }
1
+ import { formatDate, BLOCK_TITLES, numericKindOf, humanizeDuration, formatValue, formatPeriod, isExternalFactUrl, BLOCK_ORDER, humanize, qname, currencySymbolFor } from './chunk-DGP4KRWF.js';
2
+ export { BLOCK_ORDER, BLOCK_TITLES, NS, currencySymbolFor, cypherAdapter, fetchSecReportShell, fetchSecSection, formatDate, formatMoney, formatPeriod, formatValue, humanize, humanizeDuration, isExternalFactUrl, jsonldFileAdapter, mergeSecSections, numericKindOf, parseJsonld, parseStore, parseStructureDefinition, qname } from './chunk-DGP4KRWF.js';
3
+ import { useState, useEffect, useMemo, useRef } from 'react';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
31
5
 
32
6
  // src/project.ts
33
7
  function fallbackElement(id) {
@@ -81,13 +55,190 @@ function calcSubtotals(model) {
81
55
  for (const assoc of model.calcAssociations) out.add(assoc.parent);
82
56
  return out;
83
57
  }
84
- function structureForBlock(model, blockType) {
85
- return model.structures.find((s) => s.blockType === blockType) ?? null;
58
+ function sliceReportSection(model, informationBlockId) {
59
+ const ib = model.informationBlocks.find((b) => b.id === informationBlockId);
60
+ return { ...model, informationBlocks: ib ? [ib] : [] };
61
+ }
62
+ function footCheck(model, table, elementId, columnIndex) {
63
+ const kids = model.calcAssociations.filter((a) => a.parent === elementId);
64
+ if (!kids.length) return null;
65
+ const valueOf = (id) => {
66
+ const row = table.rows.find((r) => r.element.id === id && r.members.length === 0);
67
+ return row ? row.cells[columnIndex]?.value ?? null : null;
68
+ };
69
+ const terms = kids.slice().sort((a, b) => a.order - b.order).map((a) => ({
70
+ element: model.elements[a.child] ?? fallbackElement(a.child),
71
+ weight: a.weight,
72
+ value: valueOf(a.child)
73
+ }));
74
+ const present = terms.filter((t) => t.value !== null);
75
+ const expected = present.length ? present.reduce((sum, t) => sum + t.value * t.weight, 0) : null;
76
+ const actual = valueOf(elementId);
77
+ const ok = expected !== null && actual !== null && Math.abs(expected - actual) < 5e-3;
78
+ return {
79
+ element: model.elements[elementId] ?? fallbackElement(elementId),
80
+ columnIndex,
81
+ terms,
82
+ expected,
83
+ actual,
84
+ ok
85
+ };
86
+ }
87
+
88
+ // src/pivot.ts
89
+ var DOMAIN = "\u2205";
90
+ function dimKey(axis) {
91
+ return `dim:${axis}`;
92
+ }
93
+ function axisOf(key) {
94
+ return key.startsWith("dim:") ? key.slice(4) : null;
95
+ }
96
+ function dimAxes(keys) {
97
+ return keys.map(axisOf).filter((a) => a !== null);
98
+ }
99
+ function memberByAxis(fact) {
100
+ const m = /* @__PURE__ */ new Map();
101
+ for (const d of fact.dimensions ?? []) m.set(d.axis, d);
102
+ return m;
103
+ }
104
+ function coordinate(byAxis, axes) {
105
+ return axes.map((a) => `${a}=${byAxis.get(a)?.member ?? byAxis.get(a)?.typedValue ?? DOMAIN}`).join("|");
106
+ }
107
+ function sectionFacts(model, ib) {
108
+ return model.facts.filter((f) => f.factSet !== null && f.factSet === ib.factSet);
109
+ }
110
+ function localName(qname2) {
111
+ return qname2.includes(":") ? qname2.split(":").pop() : qname2;
112
+ }
113
+ function presentationNodeLocals(model, structureId) {
114
+ const out = /* @__PURE__ */ new Set();
115
+ for (const a of model.presAssociations) {
116
+ if (a.structure !== structureId) continue;
117
+ out.add(localName(a.parent));
118
+ out.add(localName(a.child));
119
+ }
120
+ return out;
121
+ }
122
+ function presentationScope(model, structureId) {
123
+ const declared = presentationNodeLocals(model, structureId);
124
+ let listsMembers = false;
125
+ for (const l of declared) {
126
+ if (l.endsWith("Member")) {
127
+ listsMembers = true;
128
+ break;
129
+ }
130
+ }
131
+ return { declared, listsMembers };
132
+ }
133
+ function dimensionInScope(scope, d) {
134
+ if (!scope.declared.has(localName(d.axis))) return false;
135
+ return !scope.listsMembers || d.member === null || scope.declared.has(localName(d.member));
136
+ }
137
+ function presentationTree(model, structureId) {
138
+ const children = /* @__PURE__ */ new Map();
139
+ const froms = /* @__PURE__ */ new Set();
140
+ const tos = /* @__PURE__ */ new Set();
141
+ for (const assoc of model.presAssociations) {
142
+ if (assoc.structure !== structureId) continue;
143
+ const kids = children.get(assoc.parent) ?? [];
144
+ kids.push([assoc.order, assoc.child]);
145
+ children.set(assoc.parent, kids);
146
+ froms.add(assoc.parent);
147
+ tos.add(assoc.child);
148
+ }
149
+ const childrenOf = /* @__PURE__ */ new Map();
150
+ for (const [parent, kids] of children) {
151
+ childrenOf.set(
152
+ parent,
153
+ kids.sort((a, b) => a[0] - b[0]).map(([, child]) => child)
154
+ );
155
+ }
156
+ const rootKey = (root) => {
157
+ const kids = children.get(root);
158
+ return kids && kids.length ? kids[0][0] : 0;
159
+ };
160
+ const roots = [...froms].filter((f) => !tos.has(f)).sort((a, b) => rootKey(a) - rootKey(b));
161
+ const indexOf = /* @__PURE__ */ new Map();
162
+ const seen = /* @__PURE__ */ new Set();
163
+ const pre = (node) => {
164
+ if (seen.has(node)) return;
165
+ seen.add(node);
166
+ const ln = localName(node);
167
+ if (!indexOf.has(ln)) indexOf.set(ln, indexOf.size);
168
+ for (const child of childrenOf.get(node) ?? []) pre(child);
169
+ };
170
+ for (const root of roots) pre(root);
171
+ return { childrenOf, roots, indexOf };
172
+ }
173
+ function structureForBlock(model, ib) {
174
+ if (ib.structureId) {
175
+ const byId = model.structures.find((s) => s.id === ib.structureId);
176
+ if (byId) return byId;
177
+ }
178
+ return model.structures.find((s) => s.blockType === ib.blockType) ?? null;
86
179
  }
87
- function columnLabel(period) {
88
- return formatDate(period.end);
180
+ function fallbackElement2(id) {
181
+ return {
182
+ id,
183
+ qname: qname(id),
184
+ label: humanize(id),
185
+ balance: null,
186
+ periodType: null,
187
+ abstract: false,
188
+ monetary: false
189
+ };
190
+ }
191
+ var elementOf = (model, id) => model.elements[id] ?? fallbackElement2(id);
192
+ function defaultPivotConfig(model, ib) {
193
+ const facts = sectionFacts(model, ib);
194
+ const structure = structureForBlock(model, ib);
195
+ const scope = structure ? presentationScope(model, structure.id) : { declared: /* @__PURE__ */ new Set(), listsMembers: false };
196
+ const membersByAxis = /* @__PURE__ */ new Map();
197
+ let hasEntity = false;
198
+ for (const f of facts) {
199
+ if (f.entity) hasEntity = true;
200
+ for (const d of f.dimensions ?? []) {
201
+ if (!dimensionInScope(scope, d)) continue;
202
+ const set = membersByAxis.get(d.axis) ?? /* @__PURE__ */ new Set();
203
+ set.add(d.member ?? d.typedValue ?? DOMAIN);
204
+ membersByAxis.set(d.axis, set);
205
+ }
206
+ }
207
+ const axes = [...membersByAxis.keys()].sort();
208
+ const memberAxes = axes.filter((a) => (membersByAxis.get(a)?.size ?? 0) > 1);
209
+ const slicerAxes = axes.filter((a) => (membersByAxis.get(a)?.size ?? 0) <= 1);
210
+ return {
211
+ rows: ["concept", ...memberAxes.map(dimKey)],
212
+ columns: ["period"],
213
+ slicers: [...hasEntity ? ["entity"] : [], ...slicerAxes.map(dimKey)],
214
+ scale: "auto",
215
+ showAbstracts: true,
216
+ dropSparsePeriods: true
217
+ };
218
+ }
219
+ function pivotDimensionsOn(config, placement) {
220
+ const isDim = (k) => k.startsWith("dim:");
221
+ const dims = [...config.rows, ...config.columns].filter(isDim);
222
+ const rows = config.rows.filter((k) => !isDim(k));
223
+ const columns = config.columns.filter((k) => !isDim(k));
224
+ if (placement === "rows") rows.push(...dims);
225
+ else columns.push(...dims);
226
+ return { ...config, rows, columns };
227
+ }
228
+ var PERIOD_DENSITY_FRACTION = 0.2;
229
+ function pruneSparsePeriods(model, facts, periods) {
230
+ const count = /* @__PURE__ */ new Map();
231
+ for (const f of facts) {
232
+ const populated = f.value !== null || f.textValue != null && f.textValue !== "";
233
+ const end = model.periods[f.period]?.end;
234
+ if (end && populated) count.set(end, (count.get(end) ?? 0) + 1);
235
+ }
236
+ const max = Math.max(0, ...count.values());
237
+ if (max < 5) return periods;
238
+ const threshold = Math.max(2, max * PERIOD_DENSITY_FRACTION);
239
+ return periods.filter((p) => (count.get(p.end) ?? 0) >= threshold);
89
240
  }
90
- function deriveColumns(model, facts) {
241
+ function derivePeriods(model, facts) {
91
242
  const byEnd = /* @__PURE__ */ new Map();
92
243
  for (const fact of facts) {
93
244
  const period = model.periods[fact.period];
@@ -101,81 +252,396 @@ function deriveColumns(model, facts) {
101
252
  const aStart = a.startDate ?? a.end;
102
253
  const bStart = b.startDate ?? b.end;
103
254
  return aStart.localeCompare(bStart) || a.end.localeCompare(b.end);
104
- }).map((period) => ({ key: period.end, label: columnLabel(period), period }));
105
- }
106
- function buildStatement(model, ib, subtotals) {
107
- const structure = structureForBlock(model, ib.blockType);
108
- const order = structure ? presentationOrder(model, structure.id) : /* @__PURE__ */ new Map();
109
- const facts = model.facts.filter((f) => f.factSet !== null && f.factSet === ib.factSet);
110
- const columns = deriveColumns(model, facts);
111
- const colIndex = new Map(columns.map((c, i) => [c.key, i]));
112
- const cellsByElement = /* @__PURE__ */ new Map();
113
- for (const fact of facts) {
114
- const period = model.periods[fact.period];
115
- if (!period) continue;
116
- const idx = colIndex.get(period.end);
117
- if (idx === void 0) continue;
118
- let cells = cellsByElement.get(fact.element);
119
- if (!cells) {
120
- cells = columns.map(() => ({ value: null, fact: null }));
121
- cellsByElement.set(fact.element, cells);
255
+ });
256
+ }
257
+ function memberCombos(facts, axes, order, domainLast) {
258
+ if (axes.length === 0) return [{ sig: "", members: [], labels: [] }];
259
+ const perAxis = axes.map((axis) => {
260
+ const byMember = /* @__PURE__ */ new Map();
261
+ let hasDomain = false;
262
+ for (const f of facts) {
263
+ const d = (f.dimensions ?? []).find((x) => x.axis === axis);
264
+ if (d) byMember.set(d.member ?? d.typedValue ?? DOMAIN, d);
265
+ else hasDomain = true;
266
+ }
267
+ const members = [...byMember.entries()].map(([key, qual]) => ({ key, qual })).sort((a, b) => {
268
+ const ao = a.qual?.member ? order.get(localName(a.qual.member)) ?? Infinity : Infinity;
269
+ const bo = b.qual?.member ? order.get(localName(b.qual.member)) ?? Infinity : Infinity;
270
+ return ao - bo || (a.qual?.memberLabel ?? a.key).localeCompare(b.qual?.memberLabel ?? b.key);
271
+ });
272
+ if (hasDomain) {
273
+ const domain = { key: DOMAIN, qual: null };
274
+ members.push(domain);
275
+ }
276
+ return { axis, members };
277
+ });
278
+ let combos = [{ sig: "", members: [], labels: [] }];
279
+ for (const { axis, members } of perAxis) {
280
+ const next = [];
281
+ for (const combo of combos) {
282
+ for (const { key, qual } of members) {
283
+ next.push({
284
+ sig: combo.sig ? `${combo.sig}|${axis}=${key}` : `${axis}=${key}`,
285
+ members: [...combo.members, ...qual ? [qual] : []],
286
+ labels: [...combo.labels, qual ? qual.memberLabel : "Total"]
287
+ });
288
+ }
122
289
  }
123
- cells[idx] = { value: fact.value, fact };
290
+ combos = next;
291
+ }
292
+ return combos;
293
+ }
294
+ function headerLevels(segsPerColumn) {
295
+ const levels = segsPerColumn[0]?.length ?? 0;
296
+ const out = [];
297
+ for (let level = 0; level < levels; level++) {
298
+ const row = [];
299
+ let i = 0;
300
+ while (i < segsPerColumn.length) {
301
+ let span = 1;
302
+ while (i + span < segsPerColumn.length && segsPerColumn[i + span][level] === segsPerColumn[i][level] && Array.from({ length: level + 1 }, (_, l) => l).every(
303
+ (l) => segsPerColumn[i + span][l] === segsPerColumn[i][l]
304
+ )) {
305
+ span++;
306
+ }
307
+ row.push({ label: segsPerColumn[i][level], span });
308
+ i += span;
309
+ }
310
+ out.push(row);
311
+ }
312
+ return out;
313
+ }
314
+ var SCALE_FACTOR = {
315
+ ones: { factor: 1, label: "" },
316
+ thousands: { factor: 1e3, label: "thousands" },
317
+ millions: { factor: 1e6, label: "millions" },
318
+ billions: { factor: 1e9, label: "billions" }
319
+ };
320
+ function resolveScale(facts, model, requested) {
321
+ const kindOf = (id) => numericKindOf(elementOf(model, id));
322
+ let factor = 1;
323
+ let label = "";
324
+ if (requested !== "auto") {
325
+ ({ factor, label } = SCALE_FACTOR[requested]);
326
+ } else {
327
+ const decimalsCount = /* @__PURE__ */ new Map();
328
+ for (const f of facts) {
329
+ if (f.value === null || kindOf(f.element) !== "monetary") continue;
330
+ const d = f.decimals === null || f.decimals === "INF" ? NaN : Number(f.decimals);
331
+ if (Number.isNaN(d) || d >= 0) continue;
332
+ decimalsCount.set(d, (decimalsCount.get(d) ?? 0) + 1);
333
+ }
334
+ let dominant = 0;
335
+ let best = 0;
336
+ for (const [d, count] of decimalsCount) {
337
+ if (count > best) {
338
+ best = count;
339
+ dominant = d;
340
+ }
341
+ }
342
+ if (dominant <= -9) ({ factor, label } = SCALE_FACTOR.billions);
343
+ else if (dominant <= -6) ({ factor, label } = SCALE_FACTOR.millions);
344
+ else if (dominant <= -3) ({ factor, label } = SCALE_FACTOR.thousands);
345
+ }
346
+ let caption = null;
347
+ if (factor > 1) {
348
+ const hasPerShare = facts.some((f) => {
349
+ const k = kindOf(f.element);
350
+ return f.value !== null && (k === "perShare" || k === "shares");
351
+ });
352
+ caption = hasPerShare ? `In ${label}, except per-share amounts` : `In ${label}`;
353
+ }
354
+ return { factor, label, caption };
355
+ }
356
+ function buildSlicers(model, facts, config) {
357
+ const slicers = [];
358
+ for (const key of config.slicers) {
359
+ if (key === "entity") {
360
+ if (model.entity) {
361
+ slicers.push({
362
+ aspect: "entity",
363
+ label: "Reporting Entity",
364
+ valueLabel: model.entity.name
365
+ });
366
+ }
367
+ continue;
368
+ }
369
+ const axis = axisOf(key);
370
+ if (!axis) continue;
371
+ let qual = null;
372
+ for (const f of facts) {
373
+ const d = (f.dimensions ?? []).find((x) => x.axis === axis);
374
+ if (d) {
375
+ qual = d;
376
+ break;
377
+ }
378
+ }
379
+ const axisLabel = qual?.axisLabel ?? humanize(axis);
380
+ slicers.push({
381
+ aspect: key,
382
+ label: `${axisLabel} [Axis]`,
383
+ valueLabel: qual ? qual.memberLabel : "Consolidated"
384
+ });
385
+ }
386
+ return slicers;
387
+ }
388
+ function buildPivot(model, ib, config = defaultPivotConfig(model, ib)) {
389
+ const structure = structureForBlock(model, ib);
390
+ const tree = structure ? presentationTree(model, structure.id) : { childrenOf: /* @__PURE__ */ new Map(), roots: [], indexOf: /* @__PURE__ */ new Map() };
391
+ const rowDimAxes = dimAxes(config.rows);
392
+ const colDimAxes = dimAxes(config.columns);
393
+ const allowedAxes = /* @__PURE__ */ new Set([...rowDimAxes, ...colDimAxes, ...dimAxes(config.slicers)]);
394
+ const scope = structure ? presentationScope(model, structure.id) : { declared: /* @__PURE__ */ new Set(), listsMembers: false };
395
+ const facts = sectionFacts(model, ib).filter(
396
+ (f) => (f.dimensions ?? []).every((d) => allowedAxes.has(d.axis) && dimensionInScope(scope, d))
397
+ );
398
+ const periodOnColumns = config.columns.includes("period");
399
+ let periods = periodOnColumns ? derivePeriods(model, facts) : [];
400
+ if (periodOnColumns && config.dropSparsePeriods !== false) {
401
+ periods = pruneSparsePeriods(model, facts, periods);
402
+ }
403
+ const colCombos = memberCombos(facts, colDimAxes, tree.indexOf);
404
+ const occupiedCols = /* @__PURE__ */ new Set();
405
+ for (const f of facts) {
406
+ const end = model.periods[f.period]?.end ?? "";
407
+ occupiedCols.add(`${end}#${coordinate(memberByAxis(f), colDimAxes)}`);
408
+ }
409
+ const columns = [];
410
+ const columnSegs = [];
411
+ const periodList = periodOnColumns ? periods : [null];
412
+ for (const period of periodList) {
413
+ for (const combo of colCombos) {
414
+ const end = period?.end ?? "";
415
+ if (!occupiedCols.has(`${end}#${combo.sig}`)) continue;
416
+ const periodLabel = period ? formatDate(period.end) : "";
417
+ columns.push({
418
+ key: `${end}#${combo.sig}`,
419
+ period,
420
+ members: combo.members,
421
+ label: combo.labels.length ? combo.labels[combo.labels.length - 1] : periodLabel
422
+ });
423
+ columnSegs.push([...periodOnColumns ? [periodLabel] : [], ...combo.labels]);
424
+ }
425
+ }
426
+ const columnHeaders = headerLevels(columnSegs);
427
+ const factIndex = /* @__PURE__ */ new Map();
428
+ for (const f of facts) {
429
+ const byAxis = memberByAxis(f);
430
+ const end = model.periods[f.period]?.end ?? "";
431
+ const rowSig = coordinate(byAxis, rowDimAxes);
432
+ const colSig = coordinate(byAxis, colDimAxes);
433
+ factIndex.set(`${f.element}\u241F${rowSig}\u241F${end}\u241F${colSig}`, f);
124
434
  }
125
- const rows = [...cellsByElement.keys()].map((id) => {
126
- const entry = order.get(id);
435
+ const factfulConcepts = new Set(facts.map((f) => f.element));
436
+ const rowCombos = memberCombos(facts, rowDimAxes, tree.indexOf);
437
+ const combosForConcept = (element) => {
438
+ if (rowDimAxes.length === 0) return rowCombos;
439
+ return rowCombos.filter(
440
+ (combo) => columns.some(
441
+ (col) => factIndex.has(
442
+ `${element}\u241F${combo.sig}\u241F${col.period?.end ?? ""}\u241F${coordinate(
443
+ new Map(col.members.map((m) => [m.axis, m])),
444
+ colDimAxes
445
+ )}`
446
+ )
447
+ )
448
+ );
449
+ };
450
+ const cellsFor = (element, rowSig) => columns.map((col) => {
451
+ const colSig = coordinate(new Map(col.members.map((m) => [m.axis, m])), colDimAxes);
452
+ const fact = factIndex.get(`${element}\u241F${rowSig}\u241F${col.period?.end ?? ""}\u241F${colSig}`);
127
453
  return {
128
- id,
129
- order: entry ? entry.order : Number.MAX_SAFE_INTEGER,
130
- depth: entry ? entry.depth : 0
454
+ value: fact?.value ?? null,
455
+ fact: fact ?? null,
456
+ textValue: fact && fact.value === null ? fact.textValue ?? null : null
131
457
  };
132
- }).sort((a, b) => a.order - b.order || a.id.localeCompare(b.id)).map(({ id, depth }) => ({
133
- element: model.elements[id] ?? fallbackElement(id),
134
- depth,
135
- isSubtotal: subtotals.has(id),
136
- cells: cellsByElement.get(id)
137
- }));
458
+ });
459
+ const subtotals = calcSubtotals(model);
460
+ const hasFactMemo = /* @__PURE__ */ new Map();
461
+ const subtreeHasFact = (id) => {
462
+ const cached = hasFactMemo.get(id);
463
+ if (cached !== void 0) return cached;
464
+ hasFactMemo.set(id, false);
465
+ let has = factfulConcepts.has(id);
466
+ for (const child of tree.childrenOf.get(id) ?? []) {
467
+ if (subtreeHasFact(child)) has = true;
468
+ }
469
+ hasFactMemo.set(id, has);
470
+ return has;
471
+ };
472
+ const rows = [];
473
+ const emitConcept = (id, depth) => {
474
+ const el = elementOf(model, id);
475
+ const combos = combosForConcept(id);
476
+ if (!combos.length) return;
477
+ if (combos.every((c) => c.members.length > 0)) {
478
+ rows.push({
479
+ key: id,
480
+ element: el,
481
+ depth,
482
+ header: false,
483
+ isSubtotal: subtotals.has(id),
484
+ members: [],
485
+ cells: columns.map(() => ({ value: null, fact: null, textValue: null }))
486
+ });
487
+ }
488
+ for (const combo of combos) {
489
+ rows.push({
490
+ key: combo.members.length ? `${id}\u241F${combo.sig}` : id,
491
+ element: el,
492
+ depth: depth + (combo.members.length ? 1 : 0),
493
+ header: false,
494
+ isSubtotal: subtotals.has(id),
495
+ members: combo.members,
496
+ cells: cellsFor(id, combo.sig)
497
+ });
498
+ }
499
+ };
500
+ const seenInTree = /* @__PURE__ */ new Set();
501
+ const walk = (id, depth) => {
502
+ if (seenInTree.has(id) || !subtreeHasFact(id)) return;
503
+ seenInTree.add(id);
504
+ const el = elementOf(model, id);
505
+ if (el.abstract) {
506
+ if (config.showAbstracts) {
507
+ rows.push({
508
+ key: id,
509
+ element: el,
510
+ depth,
511
+ header: true,
512
+ isSubtotal: false,
513
+ members: [],
514
+ cells: []
515
+ });
516
+ }
517
+ for (const child of tree.childrenOf.get(id) ?? []) walk(child, depth + 1);
518
+ } else {
519
+ for (const child of tree.childrenOf.get(id) ?? []) walk(child, depth + 1);
520
+ emitConcept(id, depth);
521
+ }
522
+ };
523
+ for (const root of tree.roots) walk(root, 0);
524
+ for (const id of factfulConcepts) {
525
+ if (seenInTree.has(id) || elementOf(model, id).abstract) continue;
526
+ emitConcept(id, 0);
527
+ }
528
+ const title = ib.structureId ? structure?.structureName ?? ib.label ?? BLOCK_TITLES[ib.blockType] ?? ib.blockType : BLOCK_TITLES[ib.blockType] ?? ib.label ?? ib.blockType;
138
529
  return {
139
530
  ib,
140
531
  blockType: ib.blockType,
141
- title: BLOCK_TITLES[ib.blockType] ?? ib.label ?? ib.blockType,
142
- structureName: structure?.structureName ?? ib.label ?? null,
532
+ title,
533
+ structureName: ib.structureId ? null : structure?.structureName ?? ib.label ?? null,
534
+ slicers: buildSlicers(model, facts, config),
535
+ columnHeaders,
143
536
  columns,
144
- rows
537
+ rows,
538
+ scale: resolveScale(facts, model, config.scale),
539
+ config
145
540
  };
146
541
  }
147
- function buildStatements(model) {
148
- const subtotals = calcSubtotals(model);
149
- return [...model.informationBlocks].sort(
150
- (a, b) => (BLOCK_ORDER[a.blockType] ?? 99) - (BLOCK_ORDER[b.blockType] ?? 99) || a.blockType.localeCompare(b.blockType)
151
- ).map((ib) => buildStatement(model, ib, subtotals));
542
+ function reportSections(model) {
543
+ return buildPivots(model).map((p) => ({ id: p.ib.id, title: p.title }));
152
544
  }
153
- function footCheck(model, statement, elementId, columnIndex) {
154
- const kids = model.calcAssociations.filter((a) => a.parent === elementId);
155
- if (!kids.length) return null;
156
- const valueOf = (id) => {
157
- const row = statement.rows.find((r) => r.element.id === id);
158
- return row ? row.cells[columnIndex]?.value ?? null : null;
159
- };
160
- const terms = kids.slice().sort((a, b) => a.order - b.order).map((a) => ({
161
- element: model.elements[a.child] ?? fallbackElement(a.child),
162
- weight: a.weight,
163
- value: valueOf(a.child)
164
- }));
165
- const present = terms.filter((t) => t.value !== null);
166
- const expected = present.length ? present.reduce((sum, t) => sum + t.value * t.weight, 0) : null;
167
- const actual = valueOf(elementId);
168
- const ok = expected !== null && actual !== null && Math.abs(expected - actual) < 5e-3;
169
- return {
170
- element: model.elements[elementId] ?? fallbackElement(elementId),
171
- columnIndex,
172
- terms,
173
- expected,
174
- actual,
175
- ok
545
+ function buildPivots(model, configFor) {
546
+ const order = (ib) => {
547
+ if (ib.structureId) {
548
+ const s = model.structures.find((x) => x.id === ib.structureId);
549
+ if (s?.order !== void 0) return s.order;
550
+ }
551
+ return BLOCK_ORDER[ib.blockType] ?? 99;
176
552
  };
553
+ return [...model.informationBlocks].sort((a, b) => order(a) - order(b) || a.blockType.localeCompare(b.blockType)).map(
554
+ (ib) => configFor ? buildPivot(model, ib, configFor(ib, defaultPivotConfig(model, ib))) : buildPivot(model, ib)
555
+ );
177
556
  }
178
557
  var styles = {
558
+ status: {
559
+ fontFamily: "var(--rs-font-sans, ui-sans-serif, system-ui, sans-serif)",
560
+ fontSize: "0.85rem",
561
+ color: "var(--rs-muted, #6b7280)",
562
+ padding: "0.75rem 0"
563
+ },
564
+ link: { color: "var(--rs-primary-700, #1d4ed8)" },
565
+ frame: { width: "100%", border: "none", display: "block" },
566
+ pre: {
567
+ whiteSpace: "pre-wrap",
568
+ wordBreak: "break-word",
569
+ fontFamily: "var(--rs-font-mono, ui-monospace, monospace)",
570
+ fontSize: "0.85rem",
571
+ color: "var(--rs-text, #111827)",
572
+ margin: 0
573
+ }
574
+ };
575
+ function wrapHtml(body) {
576
+ return `<!doctype html><html><head><meta charset="utf-8"><base target="_blank"><style>
577
+ :root { color-scheme: light; }
578
+ body { margin: 0; color: #111827;
579
+ font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
580
+ font-size: 13px; line-height: 1.45; }
581
+ img, svg { max-width: 100%; height: auto; }
582
+ table { max-width: 100%; border-collapse: collapse; }
583
+ a { color: #1d4ed8; }
584
+ </style></head><body>${body}</body></html>`;
585
+ }
586
+ function SandboxedHtml({ html }) {
587
+ const ref = useRef(null);
588
+ const fit = () => {
589
+ const frame = ref.current;
590
+ const doc = frame?.contentWindow?.document;
591
+ if (!frame || !doc) return;
592
+ try {
593
+ frame.style.height = `${doc.documentElement.scrollHeight}px`;
594
+ } catch {
595
+ }
596
+ };
597
+ return /* @__PURE__ */ jsx(
598
+ "iframe",
599
+ {
600
+ ref,
601
+ title: "Disclosure",
602
+ sandbox: "allow-same-origin",
603
+ srcDoc: wrapHtml(html),
604
+ style: styles.frame,
605
+ onLoad: () => {
606
+ fit();
607
+ setTimeout(fit, 250);
608
+ }
609
+ }
610
+ );
611
+ }
612
+ function ExternalTextBlock({ url }) {
613
+ const [state, setState] = useState("loading");
614
+ const [body, setBody] = useState("");
615
+ const isHtml = /\.html$/i.test(url);
616
+ useEffect(() => {
617
+ let cancelled = false;
618
+ setState("loading");
619
+ fetch(url).then((r) => {
620
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
621
+ return r.text();
622
+ }).then((text) => {
623
+ if (cancelled) return;
624
+ setBody(text);
625
+ setState("ready");
626
+ }).catch(() => {
627
+ if (!cancelled) setState("error");
628
+ });
629
+ return () => {
630
+ cancelled = true;
631
+ };
632
+ }, [url]);
633
+ if (state === "loading") return /* @__PURE__ */ jsx("div", { style: styles.status, children: "Loading disclosure\u2026" });
634
+ if (state === "error") {
635
+ return /* @__PURE__ */ jsxs("div", { style: styles.status, children: [
636
+ "Could not load this disclosure.",
637
+ " ",
638
+ /* @__PURE__ */ jsx("a", { style: styles.link, href: url, target: "_blank", rel: "noreferrer", children: "Open it directly \u2197" })
639
+ ] });
640
+ }
641
+ if (!isHtml) return /* @__PURE__ */ jsx("pre", { style: styles.pre, children: body });
642
+ return /* @__PURE__ */ jsx(SandboxedHtml, { html: body });
643
+ }
644
+ var styles2 = {
179
645
  panel: {
180
646
  fontFamily: "var(--rs-font-sans, ui-sans-serif, system-ui, sans-serif)",
181
647
  color: "var(--rs-text, #111827)",
@@ -192,8 +658,6 @@ var styles = {
192
658
  gap: "1rem",
193
659
  marginBottom: "0.75rem"
194
660
  },
195
- // `minWidth: 0` lets this flex child shrink below its content so the long
196
- // label / qname wrap inside the panel instead of overflowing it.
197
661
  headerText: { minWidth: 0 },
198
662
  label: { fontSize: "1rem", fontWeight: 700, margin: 0, overflowWrap: "anywhere" },
199
663
  qname: {
@@ -201,7 +665,6 @@ var styles = {
201
665
  fontFamily: "var(--rs-font-mono, ui-monospace, monospace)",
202
666
  fontSize: "0.8rem",
203
667
  color: "var(--rs-primary-700, #1d4ed8)",
204
- // qnames are single unbroken tokens — break anywhere so they don't overflow.
205
668
  overflowWrap: "anywhere"
206
669
  },
207
670
  close: {
@@ -221,6 +684,7 @@ var styles = {
221
684
  },
222
685
  key: { color: "var(--rs-muted, #6b7280)" },
223
686
  mono: { fontFamily: "var(--rs-font-mono, ui-monospace, monospace)" },
687
+ dims: { display: "grid", gap: "0.15rem" },
224
688
  footing: {
225
689
  marginTop: "0.85rem",
226
690
  paddingTop: "0.75rem",
@@ -241,84 +705,90 @@ var styles = {
241
705
  };
242
706
  function Field({ k, children }) {
243
707
  return /* @__PURE__ */ jsxs(Fragment, { children: [
244
- /* @__PURE__ */ jsx("span", { style: styles.key, children: k }),
708
+ /* @__PURE__ */ jsx("span", { style: styles2.key, children: k }),
245
709
  /* @__PURE__ */ jsx("span", { children })
246
710
  ] });
247
711
  }
248
- function FactInspector({
249
- report,
250
- statement,
251
- row,
252
- columnIndex,
253
- onClose,
254
- currencySymbol = "$"
255
- }) {
712
+ function symbolFor(unit) {
713
+ return unit?.symbol ?? currencySymbolFor(unit?.measure) ?? "$";
714
+ }
715
+ function FactInspector({ report, table, row, columnIndex, onClose }) {
256
716
  const { element } = row;
257
717
  const cell = row.cells[columnIndex];
258
- const column = statement.columns[columnIndex];
259
- const unit = cell.fact?.unit ? report.units[cell.fact.unit] : null;
260
- const foot = footCheck(report, statement, element.id, columnIndex);
261
- const sym = currencySymbol;
262
- return /* @__PURE__ */ jsxs("div", { style: styles.panel, children: [
263
- /* @__PURE__ */ jsxs("div", { style: styles.header, children: [
264
- /* @__PURE__ */ jsxs("div", { style: styles.headerText, children: [
265
- /* @__PURE__ */ jsx("p", { style: styles.label, children: element.label }),
266
- /* @__PURE__ */ jsx("span", { style: styles.qname, children: element.qname })
718
+ const fact = cell?.fact ?? null;
719
+ const period = fact ? report.periods[fact.period] : null;
720
+ const unit = fact?.unit ? report.units[fact.unit] : void 0;
721
+ const kind = numericKindOf(element);
722
+ const dims = fact?.dimensions ?? row.members;
723
+ const foot = footCheck(report, table, element.id, columnIndex);
724
+ const sym = symbolFor(unit);
725
+ return /* @__PURE__ */ jsxs("div", { style: styles2.panel, children: [
726
+ /* @__PURE__ */ jsxs("div", { style: styles2.header, children: [
727
+ /* @__PURE__ */ jsxs("div", { style: styles2.headerText, children: [
728
+ /* @__PURE__ */ jsx("p", { style: styles2.label, children: element.label }),
729
+ /* @__PURE__ */ jsx("span", { style: styles2.qname, children: element.qname })
267
730
  ] }),
268
- onClose ? /* @__PURE__ */ jsx("button", { style: styles.close, onClick: onClose, "aria-label": "Close", children: "\xD7" }) : null
731
+ onClose ? /* @__PURE__ */ jsx("button", { style: styles2.close, onClick: onClose, "aria-label": "Close", children: "\xD7" }) : null
269
732
  ] }),
270
- /* @__PURE__ */ jsxs("div", { style: styles.grid, children: [
271
- /* @__PURE__ */ jsx(Field, { k: "Value", children: /* @__PURE__ */ jsx("span", { style: styles.mono, children: formatMoney(cell.value, { currencySymbol: sym }) }) }),
272
- /* @__PURE__ */ jsxs(Field, { k: "Period", children: [
273
- formatPeriod(column.period),
733
+ /* @__PURE__ */ jsxs("div", { style: styles2.grid, children: [
734
+ /* @__PURE__ */ jsx(Field, { k: "Value", children: cell?.value === null && cell?.textValue ? /* @__PURE__ */ jsx("span", { children: humanizeDuration(cell.textValue) ?? cell.textValue }) : /* @__PURE__ */ jsx("span", { style: styles2.mono, children: formatValue(cell?.value ?? null, { numericKind: kind, symbol: sym }) }) }),
735
+ period ? /* @__PURE__ */ jsxs(Field, { k: "Period", children: [
736
+ formatPeriod(period),
274
737
  " ",
275
- /* @__PURE__ */ jsxs("span", { style: styles.key, children: [
738
+ /* @__PURE__ */ jsxs("span", { style: styles2.key, children: [
276
739
  "(",
277
- column.period.type,
740
+ period.type,
278
741
  ")"
279
742
  ] })
280
- ] }),
743
+ ] }) : null,
744
+ report.entity ? /* @__PURE__ */ jsx(Field, { k: "Entity", children: report.entity.name }) : null,
745
+ dims.length ? /* @__PURE__ */ jsx(Field, { k: "Dimensions", children: /* @__PURE__ */ jsx("span", { style: styles2.dims, children: dims.map((d) => /* @__PURE__ */ jsxs("span", { children: [
746
+ /* @__PURE__ */ jsxs("span", { style: styles2.key, children: [
747
+ d.axisLabel,
748
+ ":"
749
+ ] }),
750
+ " ",
751
+ d.explicit ? d.memberLabel : d.typedValue ?? d.memberLabel
752
+ ] }, d.axis)) }) }) : null,
281
753
  /* @__PURE__ */ jsx(Field, { k: "Unit", children: unit ? unit.label : "\u2014" }),
282
754
  element.balance ? /* @__PURE__ */ jsx(Field, { k: "Balance", children: element.balance }) : null,
283
- cell.fact?.decimals ? /* @__PURE__ */ jsx(Field, { k: "Decimals", children: cell.fact.decimals }) : null
755
+ fact?.decimals ? /* @__PURE__ */ jsx(Field, { k: "Decimals", children: fact.decimals }) : null
284
756
  ] }),
285
- foot ? /* @__PURE__ */ jsxs("div", { style: styles.footing, children: [
286
- /* @__PURE__ */ jsxs("div", { style: styles.footTitle, children: [
757
+ foot ? /* @__PURE__ */ jsxs("div", { style: styles2.footing, children: [
758
+ /* @__PURE__ */ jsxs("div", { style: styles2.footTitle, children: [
287
759
  "Calculation ",
288
760
  foot.ok ? "\u2713 foots" : "\u26A0 does not foot"
289
761
  ] }),
290
- /* @__PURE__ */ jsxs("div", { style: styles.equation, children: [
762
+ /* @__PURE__ */ jsxs("div", { style: styles2.equation, children: [
291
763
  foot.terms.map((term, i) => {
292
764
  const op = i === 0 ? term.weight < 0 ? "\u2212" : "" : term.weight < 0 ? " \u2212 " : " + ";
293
765
  return /* @__PURE__ */ jsxs("span", { title: term.element.qname, children: [
294
766
  op,
295
- formatMoney(term.value === null ? 0 : Math.abs(term.value), {
296
- currencySymbol: sym
767
+ formatValue(term.value === null ? 0 : Math.abs(term.value), {
768
+ numericKind: "monetary",
769
+ symbol: sym
297
770
  })
298
771
  ] }, term.element.id);
299
772
  }),
300
773
  " = ",
301
- /* @__PURE__ */ jsx("strong", { children: formatMoney(foot.expected, { currencySymbol: sym }) }),
774
+ /* @__PURE__ */ jsx("strong", { children: formatValue(foot.expected, { numericKind: "monetary", symbol: sym }) }),
302
775
  " ",
303
776
  foot.ok ? /* @__PURE__ */ jsx("span", { style: { color: "var(--rs-success, #00875a)" }, children: "\u2713" }) : /* @__PURE__ */ jsxs("span", { style: { color: "var(--rs-error, #dc2626)" }, children: [
304
777
  "\u2717 (reported ",
305
- formatMoney(foot.actual, { currencySymbol: sym }),
778
+ formatValue(foot.actual, { numericKind: "monetary", symbol: sym }),
306
779
  ")"
307
780
  ] })
308
781
  ] })
309
782
  ] }) : null
310
783
  ] });
311
784
  }
312
- var styles2 = {
785
+ var styles3 = {
313
786
  wrap: {
314
787
  fontFamily: "var(--rs-font-sans, ui-sans-serif, system-ui, sans-serif)",
315
788
  color: "var(--rs-text, #111827)",
316
789
  marginBottom: "2.5rem"
317
790
  },
318
- heading: {
319
- textAlign: "center",
320
- marginBottom: "1rem"
321
- },
791
+ heading: { textAlign: "center", marginBottom: "0.75rem" },
322
792
  title: {
323
793
  fontFamily: "var(--rs-font-heading, var(--rs-font-sans, sans-serif))",
324
794
  fontSize: "1.15rem",
@@ -326,28 +796,92 @@ var styles2 = {
326
796
  letterSpacing: "0.02em",
327
797
  margin: 0
328
798
  },
329
- subtitle: {
330
- fontSize: "0.85rem",
799
+ subtitle: { fontSize: "0.85rem", color: "var(--rs-muted, #6b7280)", marginTop: "0.25rem" },
800
+ scaleCaption: {
801
+ fontSize: "0.8rem",
802
+ fontStyle: "italic",
331
803
  color: "var(--rs-muted, #6b7280)",
332
804
  marginTop: "0.25rem"
333
805
  },
334
- table: {
335
- width: "100%",
336
- borderCollapse: "collapse",
337
- fontSize: "0.875rem"
806
+ slicers: {
807
+ display: "flex",
808
+ flexWrap: "wrap",
809
+ gap: "0.5rem",
810
+ margin: "0 0 0.75rem"
811
+ },
812
+ slicer: {
813
+ display: "inline-flex",
814
+ gap: "0.4rem",
815
+ alignItems: "baseline",
816
+ border: "1px solid var(--rs-border, #e5e7eb)",
817
+ borderRadius: "6px",
818
+ padding: "0.2rem 0.55rem",
819
+ fontSize: "0.78rem",
820
+ background: "var(--rs-slicer-bg, #f9fafb)"
338
821
  },
822
+ slicerLabel: {
823
+ color: "var(--rs-muted, #6b7280)",
824
+ textTransform: "uppercase",
825
+ letterSpacing: "0.03em",
826
+ fontSize: "0.68rem"
827
+ },
828
+ slicerValue: { fontWeight: 600 },
829
+ controls: {
830
+ display: "flex",
831
+ alignItems: "center",
832
+ gap: "0.5rem",
833
+ margin: "0 0 0.9rem",
834
+ fontSize: "0.8rem"
835
+ },
836
+ controlLabel: { color: "var(--rs-muted, #6b7280)" },
837
+ segmented: {
838
+ display: "inline-flex",
839
+ border: "1px solid var(--rs-border, #e5e7eb)",
840
+ borderRadius: "7px",
841
+ overflow: "hidden"
842
+ },
843
+ segment: {
844
+ border: "none",
845
+ padding: "0.25rem 0.7rem",
846
+ fontSize: "0.78rem",
847
+ cursor: "pointer",
848
+ background: "transparent",
849
+ color: "var(--rs-text, #111827)"
850
+ },
851
+ segmentActive: {
852
+ background: "var(--rs-primary-600, #2563eb)",
853
+ color: "#ffffff",
854
+ fontWeight: 600
855
+ },
856
+ scroll: { overflowX: "auto" },
857
+ table: { width: "100%", borderCollapse: "collapse", fontSize: "0.875rem" },
339
858
  th: {
340
859
  borderBottom: "2px solid var(--rs-border-strong, #9ca3af)",
341
- padding: "0.5rem 0.75rem",
860
+ padding: "0.4rem 0.75rem",
342
861
  fontWeight: 600,
343
862
  color: "var(--rs-muted, #6b7280)",
344
863
  fontSize: "0.8rem"
345
864
  },
346
- thConcept: { textAlign: "left" },
347
- thNum: { textAlign: "right", whiteSpace: "nowrap" },
348
- labelCell: {
865
+ thGroup: {
866
+ borderBottom: "1px solid var(--rs-border, #e5e7eb)",
349
867
  padding: "0.3rem 0.75rem",
350
- whiteSpace: "normal"
868
+ fontWeight: 600,
869
+ color: "var(--rs-muted, #6b7280)",
870
+ fontSize: "0.8rem",
871
+ textAlign: "center"
872
+ },
873
+ // A floor on the concept column so a wide table (many member/period columns)
874
+ // scrolls horizontally rather than squeezing labels down to vertical text.
875
+ thConcept: { textAlign: "left", verticalAlign: "bottom", minWidth: "18rem" },
876
+ thNum: { textAlign: "right", whiteSpace: "nowrap" },
877
+ labelCell: { padding: "0.3rem 0.75rem", whiteSpace: "normal", minWidth: "18rem" },
878
+ headerRowLabel: {
879
+ padding: "0.4rem 0.75rem",
880
+ fontWeight: 700,
881
+ fontSize: "0.8rem",
882
+ textTransform: "uppercase",
883
+ letterSpacing: "0.02em",
884
+ color: "var(--rs-muted, #6b7280)"
351
885
  },
352
886
  numCell: {
353
887
  padding: "0.3rem 0.75rem",
@@ -355,69 +889,171 @@ var styles2 = {
355
889
  whiteSpace: "nowrap",
356
890
  fontFamily: "var(--rs-font-mono, ui-monospace, SFMono-Regular, monospace)",
357
891
  fontVariantNumeric: "tabular-nums"
892
+ },
893
+ textBlockCell: { padding: "0.75rem", borderTop: "1px solid var(--rs-border, #e5e7eb)" },
894
+ textBlockLabel: {
895
+ fontSize: "0.7rem",
896
+ textTransform: "uppercase",
897
+ letterSpacing: "0.05em",
898
+ fontWeight: 600,
899
+ color: "var(--rs-muted, #6b7280)",
900
+ marginBottom: "0.5rem"
358
901
  }
359
902
  };
903
+ function cellSymbol(unit) {
904
+ return unit?.symbol ?? currencySymbolFor(unit?.measure) ?? "$";
905
+ }
360
906
  function StatementTable({
361
- statement,
907
+ table,
908
+ units,
362
909
  onCellClick,
363
- currencySymbol = "$",
364
- selected = null
910
+ selected = null,
911
+ dimensionPlacement,
912
+ onDimensionPlacementChange
365
913
  }) {
366
- const { columns, rows } = statement;
367
- const periodCaption = columns.length === 1 ? formatPeriod(columns[0].period) : null;
368
- return /* @__PURE__ */ jsxs("section", { style: styles2.wrap, children: [
369
- /* @__PURE__ */ jsxs("header", { style: styles2.heading, children: [
370
- /* @__PURE__ */ jsx("h3", { style: styles2.title, children: statement.title }),
371
- statement.structureName ? /* @__PURE__ */ jsx("div", { style: styles2.subtitle, children: statement.structureName }) : null,
372
- periodCaption ? /* @__PURE__ */ jsx("div", { style: styles2.subtitle, children: periodCaption }) : null
914
+ const { columns, rows, columnHeaders, scale } = table;
915
+ const totalCols = columns.length;
916
+ const dimensional = table.config.rows.some((k) => k.startsWith("dim:")) || table.config.columns.some((k) => k.startsWith("dim:"));
917
+ const showToggle = onDimensionPlacementChange != null && dimensional;
918
+ return /* @__PURE__ */ jsxs("section", { style: styles3.wrap, children: [
919
+ /* @__PURE__ */ jsxs("header", { style: styles3.heading, children: [
920
+ /* @__PURE__ */ jsx("h3", { style: styles3.title, children: table.title }),
921
+ table.structureName ? /* @__PURE__ */ jsx("div", { style: styles3.subtitle, children: table.structureName }) : null,
922
+ scale.caption ? /* @__PURE__ */ jsx("div", { style: styles3.scaleCaption, children: scale.caption }) : null
373
923
  ] }),
374
- /* @__PURE__ */ jsxs("table", { style: styles2.table, children: [
375
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
376
- /* @__PURE__ */ jsx("th", { style: { ...styles2.th, ...styles2.thConcept }, children: "Concept" }),
377
- columns.map((col) => /* @__PURE__ */ jsx("th", { style: { ...styles2.th, ...styles2.thNum }, children: col.label }, col.key))
378
- ] }) }),
924
+ table.slicers.length ? /* @__PURE__ */ jsx("div", { style: styles3.slicers, children: table.slicers.map((s) => /* @__PURE__ */ jsxs("div", { style: styles3.slicer, children: [
925
+ /* @__PURE__ */ jsx("span", { style: styles3.slicerLabel, children: s.label }),
926
+ /* @__PURE__ */ jsx("span", { style: styles3.slicerValue, children: s.valueLabel })
927
+ ] }, s.aspect)) }) : null,
928
+ showToggle ? /* @__PURE__ */ jsxs("div", { style: styles3.controls, children: [
929
+ /* @__PURE__ */ jsx("span", { style: styles3.controlLabel, children: "Dimensions" }),
930
+ /* @__PURE__ */ jsx("div", { style: styles3.segmented, role: "group", "aria-label": "Dimension placement", children: ["rows", "columns"].map((placement) => /* @__PURE__ */ jsx(
931
+ "button",
932
+ {
933
+ type: "button",
934
+ onClick: () => onDimensionPlacementChange?.(placement),
935
+ "aria-pressed": dimensionPlacement === placement,
936
+ style: {
937
+ ...styles3.segment,
938
+ ...dimensionPlacement === placement ? styles3.segmentActive : {}
939
+ },
940
+ children: placement === "rows" ? "As rows" : "As columns"
941
+ },
942
+ placement
943
+ )) })
944
+ ] }) : null,
945
+ /* @__PURE__ */ jsx("div", { style: styles3.scroll, children: /* @__PURE__ */ jsxs("table", { style: styles3.table, children: [
946
+ /* @__PURE__ */ jsx("thead", { children: columnHeaders.map((level, levelIndex) => {
947
+ const isLast = levelIndex === columnHeaders.length - 1;
948
+ let leaf = 0;
949
+ return /* @__PURE__ */ jsxs("tr", { children: [
950
+ levelIndex === 0 ? /* @__PURE__ */ jsx(
951
+ "th",
952
+ {
953
+ rowSpan: columnHeaders.length,
954
+ style: { ...styles3.th, ...styles3.thConcept },
955
+ "aria-label": "Concept"
956
+ }
957
+ ) : null,
958
+ level.map((h, i) => {
959
+ const key = `${levelIndex}-${i}-${leaf}`;
960
+ leaf += h.span;
961
+ return /* @__PURE__ */ jsx(
962
+ "th",
963
+ {
964
+ colSpan: h.span,
965
+ style: isLast ? { ...styles3.th, ...styles3.thNum } : styles3.thGroup,
966
+ children: h.label
967
+ },
968
+ key
969
+ );
970
+ })
971
+ ] }, levelIndex);
972
+ }) }),
379
973
  /* @__PURE__ */ jsx("tbody", { children: rows.map((row) => {
974
+ if (row.header) {
975
+ return /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
976
+ "td",
977
+ {
978
+ colSpan: totalCols + 1,
979
+ style: {
980
+ ...styles3.headerRowLabel,
981
+ paddingLeft: `${row.depth * 20 + 12}px`
982
+ },
983
+ title: row.element.qname,
984
+ children: row.element.label
985
+ }
986
+ ) }, row.key);
987
+ }
988
+ const textBlock = row.cells.find(
989
+ (c) => c.value === null && isExternalFactUrl(c.textValue)
990
+ );
991
+ if (textBlock?.textValue) {
992
+ return /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", { colSpan: totalCols + 1, style: styles3.textBlockCell, children: [
993
+ /* @__PURE__ */ jsx("div", { style: styles3.textBlockLabel, title: row.element.qname, children: rowLabel(row) }),
994
+ /* @__PURE__ */ jsx(ExternalTextBlock, { url: textBlock.textValue })
995
+ ] }) }, row.key);
996
+ }
997
+ const kind = numericKindOf(row.element);
380
998
  const isGrandTotal = row.isSubtotal && row.depth === 0;
381
999
  const allZeroOrNull = row.cells.every((c) => c.value === null || c.value === 0);
382
1000
  const rowStyle = {
383
1001
  backgroundColor: row.isSubtotal ? "var(--rs-subtotal-bg, #f9fafb)" : void 0
384
1002
  };
385
1003
  const labelStyle = {
386
- ...styles2.labelCell,
1004
+ ...styles3.labelCell,
387
1005
  paddingLeft: `${row.depth * 20 + 12}px`,
388
1006
  fontWeight: row.isSubtotal ? 600 : 400,
389
1007
  color: !row.isSubtotal && allZeroOrNull ? "var(--rs-muted, #9ca3af)" : void 0,
390
1008
  borderBottom: isGrandTotal ? "3px double var(--rs-text, #111827)" : void 0
391
1009
  };
392
1010
  return /* @__PURE__ */ jsxs("tr", { style: rowStyle, children: [
393
- /* @__PURE__ */ jsx("td", { style: labelStyle, title: row.element.qname, children: row.element.label }),
1011
+ /* @__PURE__ */ jsx("td", { style: labelStyle, title: row.element.qname, children: rowLabel(row) }),
394
1012
  row.cells.map((cell, i) => {
395
- const isSelected = selected !== null && selected.elementId === row.element.id && selected.columnIndex === i;
1013
+ const isSelected = selected !== null && selected.rowKey === row.key && selected.columnIndex === i;
396
1014
  const clickable = onCellClick && cell.fact !== null;
1015
+ const isText = cell.value === null && cell.textValue != null && cell.textValue !== "";
1016
+ const duration = isText ? humanizeDuration(cell.textValue) : null;
1017
+ const isProse = isText && !duration;
1018
+ const unit = cell.fact?.unit && units ? units[cell.fact.unit] : void 0;
397
1019
  const numStyle = {
398
- ...styles2.numCell,
1020
+ ...styles3.numCell,
399
1021
  fontWeight: row.isSubtotal ? 600 : 400,
400
1022
  borderBottom: isGrandTotal ? "3px double var(--rs-text, #111827)" : void 0,
401
1023
  cursor: clickable ? "pointer" : "default",
402
1024
  backgroundColor: isSelected ? "var(--rs-selected-bg, #dbeafe)" : void 0,
403
- color: !row.isSubtotal && (cell.value === null || cell.value === 0) ? "var(--rs-muted, #9ca3af)" : void 0
1025
+ color: !isText && !row.isSubtotal && (cell.value === null || cell.value === 0) ? "var(--rs-muted, #9ca3af)" : void 0,
1026
+ ...isProse ? {
1027
+ textAlign: "left",
1028
+ whiteSpace: "normal",
1029
+ fontFamily: "var(--rs-font-sans, ui-sans-serif, system-ui, sans-serif)"
1030
+ } : {}
404
1031
  };
1032
+ const display = isText ? duration ?? cell.textValue : cell.fact === null ? "" : cell.value === 0 ? "\u2014" : formatValue(cell.value, {
1033
+ numericKind: kind,
1034
+ symbol: cellSymbol(unit),
1035
+ scaleFactor: scale.factor
1036
+ });
405
1037
  return /* @__PURE__ */ jsx(
406
1038
  "td",
407
1039
  {
408
1040
  style: numStyle,
409
1041
  onClick: clickable ? () => onCellClick?.(row, i) : void 0,
410
- children: formatMoney(cell.value, { currencySymbol })
1042
+ children: display
411
1043
  },
412
1044
  columns[i].key
413
1045
  );
414
1046
  })
415
- ] }, row.element.id);
1047
+ ] }, row.key);
416
1048
  }) })
417
- ] })
1049
+ ] }) })
418
1050
  ] });
419
1051
  }
420
- var styles3 = {
1052
+ function rowLabel(row) {
1053
+ if (!row.members.length) return row.element.label;
1054
+ return row.members.map((m) => m.memberLabel).join(" \xB7 ");
1055
+ }
1056
+ var styles4 = {
421
1057
  layout: {
422
1058
  display: "grid",
423
1059
  gridTemplateColumns: "minmax(0, 1fr)",
@@ -438,52 +1074,50 @@ var styles3 = {
438
1074
  margin: 0,
439
1075
  color: "var(--rs-text, #111827)"
440
1076
  },
441
- entitySub: { fontSize: "0.85rem", color: "var(--rs-muted, #6b7280)" },
442
1077
  panel: { position: "sticky", top: "1rem" }
443
1078
  };
444
- function ReportView({ report, currencySymbol = "$", inspect = true }) {
445
- const statements = useMemo(() => buildStatements(report), [report]);
1079
+ function ReportView({ report, inspect = true, dimensionAxis = "rows" }) {
1080
+ const [dimAxis, setDimAxis] = useState(dimensionAxis);
446
1081
  const [selection, setSelection] = useState(null);
1082
+ const statements = useMemo(
1083
+ () => buildPivots(report, (_ib, cfg) => pivotDimensionsOn(cfg, dimAxis)),
1084
+ [report, dimAxis]
1085
+ );
447
1086
  const selected = selection !== null ? statements[selection.statementIndex] : null;
448
- const selectedRow = selected?.rows.find((r) => r.element.id === selection?.elementId) ?? null;
1087
+ const selectedRow = selected?.rows.find((r) => r.key === selection?.rowKey) ?? null;
1088
+ const setPlacement = (placement) => {
1089
+ setSelection(null);
1090
+ setDimAxis(placement);
1091
+ };
449
1092
  return /* @__PURE__ */ jsxs("div", { children: [
450
- report.entity ? /* @__PURE__ */ jsxs("header", { style: styles3.entity, children: [
451
- /* @__PURE__ */ jsx("h2", { style: styles3.entityName, children: report.entity.name }),
452
- report.entity.country ? /* @__PURE__ */ jsx("div", { style: styles3.entitySub, children: report.entity.country }) : null
453
- ] }) : null,
1093
+ report.entity ? /* @__PURE__ */ jsx("header", { style: styles4.entity, children: /* @__PURE__ */ jsx("h2", { style: styles4.entityName, children: report.entity.name }) }) : null,
454
1094
  /* @__PURE__ */ jsxs(
455
1095
  "div",
456
1096
  {
457
1097
  style: {
458
- ...styles3.layout,
459
- ...inspect && selectedRow ? styles3.layoutWithPanel : {}
1098
+ ...styles4.layout,
1099
+ ...inspect && selectedRow ? styles4.layoutWithPanel : {}
460
1100
  },
461
1101
  children: [
462
1102
  /* @__PURE__ */ jsx("div", { children: statements.map((statement, statementIndex) => /* @__PURE__ */ jsx(
463
1103
  StatementTable,
464
1104
  {
465
- statement,
466
- currencySymbol,
467
- selected: selection && selection.statementIndex === statementIndex ? {
468
- elementId: selection.elementId,
469
- columnIndex: selection.columnIndex
470
- } : null,
471
- onCellClick: inspect ? (row, columnIndex) => setSelection({
472
- statementIndex,
473
- elementId: row.element.id,
474
- columnIndex
475
- }) : void 0
1105
+ table: statement,
1106
+ units: report.units,
1107
+ dimensionPlacement: dimAxis,
1108
+ onDimensionPlacementChange: setPlacement,
1109
+ selected: selection && selection.statementIndex === statementIndex ? { rowKey: selection.rowKey, columnIndex: selection.columnIndex } : null,
1110
+ onCellClick: inspect ? (row, columnIndex) => setSelection({ statementIndex, rowKey: row.key, columnIndex }) : void 0
476
1111
  },
477
1112
  statement.ib.id
478
1113
  )) }),
479
- inspect && selected && selectedRow && selection ? /* @__PURE__ */ jsx("div", { style: styles3.panel, children: /* @__PURE__ */ jsx(
1114
+ inspect && selected && selectedRow && selection ? /* @__PURE__ */ jsx("div", { style: styles4.panel, children: /* @__PURE__ */ jsx(
480
1115
  FactInspector,
481
1116
  {
482
1117
  report,
483
- statement: selected,
1118
+ table: selected,
484
1119
  row: selectedRow,
485
1120
  columnIndex: selection.columnIndex,
486
- currencySymbol,
487
1121
  onClose: () => setSelection(null)
488
1122
  }
489
1123
  ) }) : null
@@ -493,6 +1127,6 @@ function ReportView({ report, currencySymbol = "$", inspect = true }) {
493
1127
  ] });
494
1128
  }
495
1129
 
496
- export { FactInspector, ReportView, StatementTable, buildStatements, calcSubtotals, footCheck, formatDate, formatMoney, formatPeriod, presentationOrder };
1130
+ export { ExternalTextBlock, FactInspector, ReportView, StatementTable, buildPivot, buildPivots, calcSubtotals, defaultPivotConfig, footCheck, pivotDimensionsOn, presentationOrder, reportSections, sliceReportSection };
497
1131
  //# sourceMappingURL=index.js.map
498
1132
  //# sourceMappingURL=index.js.map