@robosystems/report-components 0.2.0 → 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,34 +1,8 @@
1
- import { isExternalFactUrl, BLOCK_ORDER, BLOCK_TITLES, humanize, qname } from './chunk-POSLHOTN.js';
2
- export { BLOCK_ORDER, BLOCK_TITLES, NS, cypherAdapter, fetchSecReportShell, fetchSecSection, humanize, isExternalFactUrl, jsonldFileAdapter, mergeSecSections, parseJsonld, parseStore, parseStructureDefinition, qname } from './chunk-POSLHOTN.js';
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
3
  import { useState, useEffect, useMemo, useRef } from 'react';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
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
- }
31
-
32
6
  // src/project.ts
33
7
  function fallbackElement(id) {
34
8
  return {
@@ -81,6 +55,121 @@ function calcSubtotals(model) {
81
55
  for (const assoc of model.calcAssociations) out.add(assoc.parent);
82
56
  return out;
83
57
  }
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
+ }
84
173
  function structureForBlock(model, ib) {
85
174
  if (ib.structureId) {
86
175
  const byId = model.structures.find((s) => s.id === ib.structureId);
@@ -88,17 +177,68 @@ function structureForBlock(model, ib) {
88
177
  }
89
178
  return model.structures.find((s) => s.blockType === ib.blockType) ?? null;
90
179
  }
91
- function blockOrder(model, ib) {
92
- if (ib.structureId) {
93
- const s = model.structures.find((x) => x.id === ib.structureId);
94
- if (s?.order !== void 0) return s.order;
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
+ }
95
206
  }
96
- return BLOCK_ORDER[ib.blockType] ?? 99;
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
+ };
97
218
  }
98
- function columnLabel(period) {
99
- return formatDate(period.end);
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);
100
240
  }
101
- function deriveColumns(model, facts) {
241
+ function derivePeriods(model, facts) {
102
242
  const byEnd = /* @__PURE__ */ new Map();
103
243
  for (const fact of facts) {
104
244
  const period = model.periods[fact.period];
@@ -112,87 +252,307 @@ function deriveColumns(model, facts) {
112
252
  const aStart = a.startDate ?? a.end;
113
253
  const bStart = b.startDate ?? b.end;
114
254
  return aStart.localeCompare(bStart) || a.end.localeCompare(b.end);
115
- }).map((period) => ({ key: period.end, label: columnLabel(period), period }));
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
+ }
289
+ }
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;
116
387
  }
117
- function buildStatement(model, ib, subtotals) {
388
+ function buildPivot(model, ib, config = defaultPivotConfig(model, ib)) {
118
389
  const structure = structureForBlock(model, ib);
119
- const order = structure ? presentationOrder(model, structure.id) : /* @__PURE__ */ new Map();
120
- const facts = model.facts.filter((f) => f.factSet !== null && f.factSet === ib.factSet);
121
- const columns = deriveColumns(model, facts);
122
- const colIndex = new Map(columns.map((c, i) => [c.key, i]));
123
- const cellsByElement = /* @__PURE__ */ new Map();
124
- for (const fact of facts) {
125
- const period = model.periods[fact.period];
126
- if (!period) continue;
127
- const idx = colIndex.get(period.end);
128
- if (idx === void 0) continue;
129
- let cells = cellsByElement.get(fact.element);
130
- if (!cells) {
131
- cells = columns.map(() => ({ value: null, fact: null, textValue: null }));
132
- cellsByElement.set(fact.element, cells);
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]);
133
424
  }
134
- cells[idx] = { value: fact.value, fact, textValue: fact.textValue ?? null };
135
425
  }
136
- const rows = [...cellsByElement.keys()].map((id) => {
137
- const entry = order.get(id);
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);
434
+ }
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}`);
138
453
  return {
139
- id,
140
- order: entry ? entry.order : Number.MAX_SAFE_INTEGER,
141
- depth: entry ? entry.depth : 0
454
+ value: fact?.value ?? null,
455
+ fact: fact ?? null,
456
+ textValue: fact && fact.value === null ? fact.textValue ?? null : null
142
457
  };
143
- }).sort((a, b) => a.order - b.order || a.id.localeCompare(b.id)).map(({ id, depth }) => ({
144
- element: model.elements[id] ?? fallbackElement(id),
145
- depth,
146
- isSubtotal: subtotals.has(id),
147
- cells: cellsByElement.get(id)
148
- }));
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
+ }
149
528
  const title = ib.structureId ? structure?.structureName ?? ib.label ?? BLOCK_TITLES[ib.blockType] ?? ib.blockType : BLOCK_TITLES[ib.blockType] ?? ib.label ?? ib.blockType;
150
529
  return {
151
530
  ib,
152
531
  blockType: ib.blockType,
153
532
  title,
154
533
  structureName: ib.structureId ? null : structure?.structureName ?? ib.label ?? null,
534
+ slicers: buildSlicers(model, facts, config),
535
+ columnHeaders,
155
536
  columns,
156
- rows
537
+ rows,
538
+ scale: resolveScale(facts, model, config.scale),
539
+ config
157
540
  };
158
541
  }
159
542
  function reportSections(model) {
160
- return buildStatements(model).map((s) => ({ id: s.ib.id, title: s.title }));
543
+ return buildPivots(model).map((p) => ({ id: p.ib.id, title: p.title }));
161
544
  }
162
- function sliceReportSection(model, informationBlockId) {
163
- const ib = model.informationBlocks.find((b) => b.id === informationBlockId);
164
- return { ...model, informationBlocks: ib ? [ib] : [] };
165
- }
166
- function buildStatements(model) {
167
- const subtotals = calcSubtotals(model);
168
- return [...model.informationBlocks].sort(
169
- (a, b) => blockOrder(model, a) - blockOrder(model, b) || a.blockType.localeCompare(b.blockType)
170
- ).map((ib) => buildStatement(model, ib, subtotals));
171
- }
172
- function footCheck(model, statement, elementId, columnIndex) {
173
- const kids = model.calcAssociations.filter((a) => a.parent === elementId);
174
- if (!kids.length) return null;
175
- const valueOf = (id) => {
176
- const row = statement.rows.find((r) => r.element.id === id);
177
- return row ? row.cells[columnIndex]?.value ?? null : null;
178
- };
179
- const terms = kids.slice().sort((a, b) => a.order - b.order).map((a) => ({
180
- element: model.elements[a.child] ?? fallbackElement(a.child),
181
- weight: a.weight,
182
- value: valueOf(a.child)
183
- }));
184
- const present = terms.filter((t) => t.value !== null);
185
- const expected = present.length ? present.reduce((sum, t) => sum + t.value * t.weight, 0) : null;
186
- const actual = valueOf(elementId);
187
- const ok = expected !== null && actual !== null && Math.abs(expected - actual) < 5e-3;
188
- return {
189
- element: model.elements[elementId] ?? fallbackElement(elementId),
190
- columnIndex,
191
- terms,
192
- expected,
193
- actual,
194
- 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;
195
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
+ );
196
556
  }
197
557
  var styles = {
198
558
  status: {
@@ -298,8 +658,6 @@ var styles2 = {
298
658
  gap: "1rem",
299
659
  marginBottom: "0.75rem"
300
660
  },
301
- // `minWidth: 0` lets this flex child shrink below its content so the long
302
- // label / qname wrap inside the panel instead of overflowing it.
303
661
  headerText: { minWidth: 0 },
304
662
  label: { fontSize: "1rem", fontWeight: 700, margin: 0, overflowWrap: "anywhere" },
305
663
  qname: {
@@ -307,7 +665,6 @@ var styles2 = {
307
665
  fontFamily: "var(--rs-font-mono, ui-monospace, monospace)",
308
666
  fontSize: "0.8rem",
309
667
  color: "var(--rs-primary-700, #1d4ed8)",
310
- // qnames are single unbroken tokens — break anywhere so they don't overflow.
311
668
  overflowWrap: "anywhere"
312
669
  },
313
670
  close: {
@@ -327,6 +684,7 @@ var styles2 = {
327
684
  },
328
685
  key: { color: "var(--rs-muted, #6b7280)" },
329
686
  mono: { fontFamily: "var(--rs-font-mono, ui-monospace, monospace)" },
687
+ dims: { display: "grid", gap: "0.15rem" },
330
688
  footing: {
331
689
  marginTop: "0.85rem",
332
690
  paddingTop: "0.75rem",
@@ -351,20 +709,19 @@ function Field({ k, children }) {
351
709
  /* @__PURE__ */ jsx("span", { children })
352
710
  ] });
353
711
  }
354
- function FactInspector({
355
- report,
356
- statement,
357
- row,
358
- columnIndex,
359
- onClose,
360
- currencySymbol = "$"
361
- }) {
712
+ function symbolFor(unit) {
713
+ return unit?.symbol ?? currencySymbolFor(unit?.measure) ?? "$";
714
+ }
715
+ function FactInspector({ report, table, row, columnIndex, onClose }) {
362
716
  const { element } = row;
363
717
  const cell = row.cells[columnIndex];
364
- const column = statement.columns[columnIndex];
365
- const unit = cell.fact?.unit ? report.units[cell.fact.unit] : null;
366
- const foot = footCheck(report, statement, element.id, columnIndex);
367
- const sym = currencySymbol;
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);
368
725
  return /* @__PURE__ */ jsxs("div", { style: styles2.panel, children: [
369
726
  /* @__PURE__ */ jsxs("div", { style: styles2.header, children: [
370
727
  /* @__PURE__ */ jsxs("div", { style: styles2.headerText, children: [
@@ -374,19 +731,28 @@ function FactInspector({
374
731
  onClose ? /* @__PURE__ */ jsx("button", { style: styles2.close, onClick: onClose, "aria-label": "Close", children: "\xD7" }) : null
375
732
  ] }),
376
733
  /* @__PURE__ */ jsxs("div", { style: styles2.grid, children: [
377
- /* @__PURE__ */ jsx(Field, { k: "Value", children: cell.value === null && cell.textValue ? /* @__PURE__ */ jsx("span", { children: cell.textValue }) : /* @__PURE__ */ jsx("span", { style: styles2.mono, children: formatMoney(cell.value, { currencySymbol: sym }) }) }),
378
- /* @__PURE__ */ jsxs(Field, { k: "Period", children: [
379
- formatPeriod(column.period),
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),
380
737
  " ",
381
738
  /* @__PURE__ */ jsxs("span", { style: styles2.key, children: [
382
739
  "(",
383
- column.period.type,
740
+ period.type,
384
741
  ")"
385
742
  ] })
386
- ] }),
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,
387
753
  /* @__PURE__ */ jsx(Field, { k: "Unit", children: unit ? unit.label : "\u2014" }),
388
754
  element.balance ? /* @__PURE__ */ jsx(Field, { k: "Balance", children: element.balance }) : null,
389
- 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
390
756
  ] }),
391
757
  foot ? /* @__PURE__ */ jsxs("div", { style: styles2.footing, children: [
392
758
  /* @__PURE__ */ jsxs("div", { style: styles2.footTitle, children: [
@@ -398,17 +764,18 @@ function FactInspector({
398
764
  const op = i === 0 ? term.weight < 0 ? "\u2212" : "" : term.weight < 0 ? " \u2212 " : " + ";
399
765
  return /* @__PURE__ */ jsxs("span", { title: term.element.qname, children: [
400
766
  op,
401
- formatMoney(term.value === null ? 0 : Math.abs(term.value), {
402
- currencySymbol: sym
767
+ formatValue(term.value === null ? 0 : Math.abs(term.value), {
768
+ numericKind: "monetary",
769
+ symbol: sym
403
770
  })
404
771
  ] }, term.element.id);
405
772
  }),
406
773
  " = ",
407
- /* @__PURE__ */ jsx("strong", { children: formatMoney(foot.expected, { currencySymbol: sym }) }),
774
+ /* @__PURE__ */ jsx("strong", { children: formatValue(foot.expected, { numericKind: "monetary", symbol: sym }) }),
408
775
  " ",
409
776
  foot.ok ? /* @__PURE__ */ jsx("span", { style: { color: "var(--rs-success, #00875a)" }, children: "\u2713" }) : /* @__PURE__ */ jsxs("span", { style: { color: "var(--rs-error, #dc2626)" }, children: [
410
777
  "\u2717 (reported ",
411
- formatMoney(foot.actual, { currencySymbol: sym }),
778
+ formatValue(foot.actual, { numericKind: "monetary", symbol: sym }),
412
779
  ")"
413
780
  ] })
414
781
  ] })
@@ -421,10 +788,7 @@ var styles3 = {
421
788
  color: "var(--rs-text, #111827)",
422
789
  marginBottom: "2.5rem"
423
790
  },
424
- heading: {
425
- textAlign: "center",
426
- marginBottom: "1rem"
427
- },
791
+ heading: { textAlign: "center", marginBottom: "0.75rem" },
428
792
  title: {
429
793
  fontFamily: "var(--rs-font-heading, var(--rs-font-sans, sans-serif))",
430
794
  fontSize: "1.15rem",
@@ -432,28 +796,92 @@ var styles3 = {
432
796
  letterSpacing: "0.02em",
433
797
  margin: 0
434
798
  },
435
- subtitle: {
436
- 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",
437
803
  color: "var(--rs-muted, #6b7280)",
438
804
  marginTop: "0.25rem"
439
805
  },
440
- table: {
441
- width: "100%",
442
- borderCollapse: "collapse",
443
- 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)"
821
+ },
822
+ slicerLabel: {
823
+ color: "var(--rs-muted, #6b7280)",
824
+ textTransform: "uppercase",
825
+ letterSpacing: "0.03em",
826
+ fontSize: "0.68rem"
444
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" },
445
858
  th: {
446
859
  borderBottom: "2px solid var(--rs-border-strong, #9ca3af)",
447
- padding: "0.5rem 0.75rem",
860
+ padding: "0.4rem 0.75rem",
448
861
  fontWeight: 600,
449
862
  color: "var(--rs-muted, #6b7280)",
450
863
  fontSize: "0.8rem"
451
864
  },
452
- thConcept: { textAlign: "left" },
453
- thNum: { textAlign: "right", whiteSpace: "nowrap" },
454
- labelCell: {
865
+ thGroup: {
866
+ borderBottom: "1px solid var(--rs-border, #e5e7eb)",
455
867
  padding: "0.3rem 0.75rem",
456
- 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)"
457
885
  },
458
886
  numCell: {
459
887
  padding: "0.3rem 0.75rem",
@@ -462,10 +890,7 @@ var styles3 = {
462
890
  fontFamily: "var(--rs-font-mono, ui-monospace, SFMono-Regular, monospace)",
463
891
  fontVariantNumeric: "tabular-nums"
464
892
  },
465
- textBlockCell: {
466
- padding: "0.75rem",
467
- borderTop: "1px solid var(--rs-border, #e5e7eb)"
468
- },
893
+ textBlockCell: { padding: "0.75rem", borderTop: "1px solid var(--rs-border, #e5e7eb)" },
469
894
  textBlockLabel: {
470
895
  fontSize: "0.7rem",
471
896
  textTransform: "uppercase",
@@ -475,35 +900,101 @@ var styles3 = {
475
900
  marginBottom: "0.5rem"
476
901
  }
477
902
  };
903
+ function cellSymbol(unit) {
904
+ return unit?.symbol ?? currencySymbolFor(unit?.measure) ?? "$";
905
+ }
478
906
  function StatementTable({
479
- statement,
907
+ table,
908
+ units,
480
909
  onCellClick,
481
- currencySymbol = "$",
482
- selected = null
910
+ selected = null,
911
+ dimensionPlacement,
912
+ onDimensionPlacementChange
483
913
  }) {
484
- const { columns, rows } = statement;
485
- const periodCaption = columns.length === 1 ? formatPeriod(columns[0].period) : 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;
486
918
  return /* @__PURE__ */ jsxs("section", { style: styles3.wrap, children: [
487
919
  /* @__PURE__ */ jsxs("header", { style: styles3.heading, children: [
488
- /* @__PURE__ */ jsx("h3", { style: styles3.title, children: statement.title }),
489
- statement.structureName ? /* @__PURE__ */ jsx("div", { style: styles3.subtitle, children: statement.structureName }) : null,
490
- periodCaption ? /* @__PURE__ */ jsx("div", { style: styles3.subtitle, children: periodCaption }) : null
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
491
923
  ] }),
492
- /* @__PURE__ */ jsxs("table", { style: styles3.table, children: [
493
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
494
- /* @__PURE__ */ jsx("th", { style: { ...styles3.th, ...styles3.thConcept }, children: "Concept" }),
495
- columns.map((col) => /* @__PURE__ */ jsx("th", { style: { ...styles3.th, ...styles3.thNum }, children: col.label }, col.key))
496
- ] }) }),
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
+ }) }),
497
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
+ }
498
988
  const textBlock = row.cells.find(
499
989
  (c) => c.value === null && isExternalFactUrl(c.textValue)
500
990
  );
501
991
  if (textBlock?.textValue) {
502
- return /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", { colSpan: columns.length + 1, style: styles3.textBlockCell, children: [
503
- /* @__PURE__ */ jsx("div", { style: styles3.textBlockLabel, title: row.element.qname, children: row.element.label }),
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) }),
504
994
  /* @__PURE__ */ jsx(ExternalTextBlock, { url: textBlock.textValue })
505
- ] }) }, row.element.id);
995
+ ] }) }, row.key);
506
996
  }
997
+ const kind = numericKindOf(row.element);
507
998
  const isGrandTotal = row.isSubtotal && row.depth === 0;
508
999
  const allZeroOrNull = row.cells.every((c) => c.value === null || c.value === 0);
509
1000
  const rowStyle = {
@@ -517,11 +1008,14 @@ function StatementTable({
517
1008
  borderBottom: isGrandTotal ? "3px double var(--rs-text, #111827)" : void 0
518
1009
  };
519
1010
  return /* @__PURE__ */ jsxs("tr", { style: rowStyle, children: [
520
- /* @__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) }),
521
1012
  row.cells.map((cell, i) => {
522
- const isSelected = selected !== null && selected.elementId === row.element.id && selected.columnIndex === i;
1013
+ const isSelected = selected !== null && selected.rowKey === row.key && selected.columnIndex === i;
523
1014
  const clickable = onCellClick && cell.fact !== null;
524
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;
525
1019
  const numStyle = {
526
1020
  ...styles3.numCell,
527
1021
  fontWeight: row.isSubtotal ? 600 : 400,
@@ -529,27 +1023,36 @@ function StatementTable({
529
1023
  cursor: clickable ? "pointer" : "default",
530
1024
  backgroundColor: isSelected ? "var(--rs-selected-bg, #dbeafe)" : void 0,
531
1025
  color: !isText && !row.isSubtotal && (cell.value === null || cell.value === 0) ? "var(--rs-muted, #9ca3af)" : void 0,
532
- ...isText ? {
1026
+ ...isProse ? {
533
1027
  textAlign: "left",
534
1028
  whiteSpace: "normal",
535
1029
  fontFamily: "var(--rs-font-sans, ui-sans-serif, system-ui, sans-serif)"
536
1030
  } : {}
537
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
+ });
538
1037
  return /* @__PURE__ */ jsx(
539
1038
  "td",
540
1039
  {
541
1040
  style: numStyle,
542
1041
  onClick: clickable ? () => onCellClick?.(row, i) : void 0,
543
- children: isText ? cell.textValue : formatMoney(cell.value, { currencySymbol })
1042
+ children: display
544
1043
  },
545
1044
  columns[i].key
546
1045
  );
547
1046
  })
548
- ] }, row.element.id);
1047
+ ] }, row.key);
549
1048
  }) })
550
- ] })
1049
+ ] }) })
551
1050
  ] });
552
1051
  }
1052
+ function rowLabel(row) {
1053
+ if (!row.members.length) return row.element.label;
1054
+ return row.members.map((m) => m.memberLabel).join(" \xB7 ");
1055
+ }
553
1056
  var styles4 = {
554
1057
  layout: {
555
1058
  display: "grid",
@@ -573,11 +1076,19 @@ var styles4 = {
573
1076
  },
574
1077
  panel: { position: "sticky", top: "1rem" }
575
1078
  };
576
- function ReportView({ report, currencySymbol = "$", inspect = true }) {
577
- const statements = useMemo(() => buildStatements(report), [report]);
1079
+ function ReportView({ report, inspect = true, dimensionAxis = "rows" }) {
1080
+ const [dimAxis, setDimAxis] = useState(dimensionAxis);
578
1081
  const [selection, setSelection] = useState(null);
1082
+ const statements = useMemo(
1083
+ () => buildPivots(report, (_ib, cfg) => pivotDimensionsOn(cfg, dimAxis)),
1084
+ [report, dimAxis]
1085
+ );
579
1086
  const selected = selection !== null ? statements[selection.statementIndex] : null;
580
- 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
+ };
581
1092
  return /* @__PURE__ */ jsxs("div", { children: [
582
1093
  report.entity ? /* @__PURE__ */ jsx("header", { style: styles4.entity, children: /* @__PURE__ */ jsx("h2", { style: styles4.entityName, children: report.entity.name }) }) : null,
583
1094
  /* @__PURE__ */ jsxs(
@@ -591,17 +1102,12 @@ function ReportView({ report, currencySymbol = "$", inspect = true }) {
591
1102
  /* @__PURE__ */ jsx("div", { children: statements.map((statement, statementIndex) => /* @__PURE__ */ jsx(
592
1103
  StatementTable,
593
1104
  {
594
- statement,
595
- currencySymbol,
596
- selected: selection && selection.statementIndex === statementIndex ? {
597
- elementId: selection.elementId,
598
- columnIndex: selection.columnIndex
599
- } : null,
600
- onCellClick: inspect ? (row, columnIndex) => setSelection({
601
- statementIndex,
602
- elementId: row.element.id,
603
- columnIndex
604
- }) : 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
605
1111
  },
606
1112
  statement.ib.id
607
1113
  )) }),
@@ -609,10 +1115,9 @@ function ReportView({ report, currencySymbol = "$", inspect = true }) {
609
1115
  FactInspector,
610
1116
  {
611
1117
  report,
612
- statement: selected,
1118
+ table: selected,
613
1119
  row: selectedRow,
614
1120
  columnIndex: selection.columnIndex,
615
- currencySymbol,
616
1121
  onClose: () => setSelection(null)
617
1122
  }
618
1123
  ) }) : null
@@ -622,6 +1127,6 @@ function ReportView({ report, currencySymbol = "$", inspect = true }) {
622
1127
  ] });
623
1128
  }
624
1129
 
625
- export { ExternalTextBlock, FactInspector, ReportView, StatementTable, buildStatements, calcSubtotals, footCheck, formatDate, formatMoney, formatPeriod, presentationOrder, reportSections, sliceReportSection };
1130
+ export { ExternalTextBlock, FactInspector, ReportView, StatementTable, buildPivot, buildPivots, calcSubtotals, defaultPivotConfig, footCheck, pivotDimensionsOn, presentationOrder, reportSections, sliceReportSection };
626
1131
  //# sourceMappingURL=index.js.map
627
1132
  //# sourceMappingURL=index.js.map