@robosystems/report-components 0.2.0 → 0.2.2

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-RQXAMFTZ.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-RQXAMFTZ.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
+ };
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 };
97
227
  }
98
- function columnLabel(period) {
99
- return formatDate(period.end);
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 }));
161
- }
162
- function sliceReportSection(model, informationBlockId) {
163
- const ib = model.informationBlocks.find((b) => b.id === informationBlockId);
164
- return { ...model, informationBlocks: ib ? [ib] : [] };
543
+ return buildPivots(model).map((p) => ({ id: p.ib.id, title: p.title }));
165
544
  }
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: {
@@ -223,6 +583,9 @@ function wrapHtml(body) {
223
583
  a { color: #1d4ed8; }
224
584
  </style></head><body>${body}</body></html>`;
225
585
  }
586
+ function InlineTextBlock({ html }) {
587
+ return /* @__PURE__ */ jsx(SandboxedHtml, { html });
588
+ }
226
589
  function SandboxedHtml({ html }) {
227
590
  const ref = useRef(null);
228
591
  const fit = () => {
@@ -298,8 +661,6 @@ var styles2 = {
298
661
  gap: "1rem",
299
662
  marginBottom: "0.75rem"
300
663
  },
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
664
  headerText: { minWidth: 0 },
304
665
  label: { fontSize: "1rem", fontWeight: 700, margin: 0, overflowWrap: "anywhere" },
305
666
  qname: {
@@ -307,7 +668,6 @@ var styles2 = {
307
668
  fontFamily: "var(--rs-font-mono, ui-monospace, monospace)",
308
669
  fontSize: "0.8rem",
309
670
  color: "var(--rs-primary-700, #1d4ed8)",
310
- // qnames are single unbroken tokens — break anywhere so they don't overflow.
311
671
  overflowWrap: "anywhere"
312
672
  },
313
673
  close: {
@@ -327,6 +687,7 @@ var styles2 = {
327
687
  },
328
688
  key: { color: "var(--rs-muted, #6b7280)" },
329
689
  mono: { fontFamily: "var(--rs-font-mono, ui-monospace, monospace)" },
690
+ dims: { display: "grid", gap: "0.15rem" },
330
691
  footing: {
331
692
  marginTop: "0.85rem",
332
693
  paddingTop: "0.75rem",
@@ -351,20 +712,19 @@ function Field({ k, children }) {
351
712
  /* @__PURE__ */ jsx("span", { children })
352
713
  ] });
353
714
  }
354
- function FactInspector({
355
- report,
356
- statement,
357
- row,
358
- columnIndex,
359
- onClose,
360
- currencySymbol = "$"
361
- }) {
715
+ function symbolFor(unit) {
716
+ return unit?.symbol ?? currencySymbolFor(unit?.measure) ?? "$";
717
+ }
718
+ function FactInspector({ report, table, row, columnIndex, onClose }) {
362
719
  const { element } = row;
363
720
  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;
721
+ const fact = cell?.fact ?? null;
722
+ const period = fact ? report.periods[fact.period] : null;
723
+ const unit = fact?.unit ? report.units[fact.unit] : void 0;
724
+ const kind = numericKindOf(element);
725
+ const dims = fact?.dimensions ?? row.members;
726
+ const foot = footCheck(report, table, element.id, columnIndex);
727
+ const sym = symbolFor(unit);
368
728
  return /* @__PURE__ */ jsxs("div", { style: styles2.panel, children: [
369
729
  /* @__PURE__ */ jsxs("div", { style: styles2.header, children: [
370
730
  /* @__PURE__ */ jsxs("div", { style: styles2.headerText, children: [
@@ -374,19 +734,28 @@ function FactInspector({
374
734
  onClose ? /* @__PURE__ */ jsx("button", { style: styles2.close, onClick: onClose, "aria-label": "Close", children: "\xD7" }) : null
375
735
  ] }),
376
736
  /* @__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),
737
+ /* @__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 }) }) }),
738
+ period ? /* @__PURE__ */ jsxs(Field, { k: "Period", children: [
739
+ formatPeriod(period),
380
740
  " ",
381
741
  /* @__PURE__ */ jsxs("span", { style: styles2.key, children: [
382
742
  "(",
383
- column.period.type,
743
+ period.type,
384
744
  ")"
385
745
  ] })
386
- ] }),
746
+ ] }) : null,
747
+ report.entity ? /* @__PURE__ */ jsx(Field, { k: "Entity", children: report.entity.name }) : null,
748
+ dims.length ? /* @__PURE__ */ jsx(Field, { k: "Dimensions", children: /* @__PURE__ */ jsx("span", { style: styles2.dims, children: dims.map((d) => /* @__PURE__ */ jsxs("span", { children: [
749
+ /* @__PURE__ */ jsxs("span", { style: styles2.key, children: [
750
+ d.axisLabel,
751
+ ":"
752
+ ] }),
753
+ " ",
754
+ d.explicit ? d.memberLabel : d.typedValue ?? d.memberLabel
755
+ ] }, d.axis)) }) }) : null,
387
756
  /* @__PURE__ */ jsx(Field, { k: "Unit", children: unit ? unit.label : "\u2014" }),
388
757
  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
758
+ fact?.decimals ? /* @__PURE__ */ jsx(Field, { k: "Decimals", children: fact.decimals }) : null
390
759
  ] }),
391
760
  foot ? /* @__PURE__ */ jsxs("div", { style: styles2.footing, children: [
392
761
  /* @__PURE__ */ jsxs("div", { style: styles2.footTitle, children: [
@@ -398,17 +767,18 @@ function FactInspector({
398
767
  const op = i === 0 ? term.weight < 0 ? "\u2212" : "" : term.weight < 0 ? " \u2212 " : " + ";
399
768
  return /* @__PURE__ */ jsxs("span", { title: term.element.qname, children: [
400
769
  op,
401
- formatMoney(term.value === null ? 0 : Math.abs(term.value), {
402
- currencySymbol: sym
770
+ formatValue(term.value === null ? 0 : Math.abs(term.value), {
771
+ numericKind: "monetary",
772
+ symbol: sym
403
773
  })
404
774
  ] }, term.element.id);
405
775
  }),
406
776
  " = ",
407
- /* @__PURE__ */ jsx("strong", { children: formatMoney(foot.expected, { currencySymbol: sym }) }),
777
+ /* @__PURE__ */ jsx("strong", { children: formatValue(foot.expected, { numericKind: "monetary", symbol: sym }) }),
408
778
  " ",
409
779
  foot.ok ? /* @__PURE__ */ jsx("span", { style: { color: "var(--rs-success, #00875a)" }, children: "\u2713" }) : /* @__PURE__ */ jsxs("span", { style: { color: "var(--rs-error, #dc2626)" }, children: [
410
780
  "\u2717 (reported ",
411
- formatMoney(foot.actual, { currencySymbol: sym }),
781
+ formatValue(foot.actual, { numericKind: "monetary", symbol: sym }),
412
782
  ")"
413
783
  ] })
414
784
  ] })
@@ -421,10 +791,7 @@ var styles3 = {
421
791
  color: "var(--rs-text, #111827)",
422
792
  marginBottom: "2.5rem"
423
793
  },
424
- heading: {
425
- textAlign: "center",
426
- marginBottom: "1rem"
427
- },
794
+ heading: { textAlign: "center", marginBottom: "0.75rem" },
428
795
  title: {
429
796
  fontFamily: "var(--rs-font-heading, var(--rs-font-sans, sans-serif))",
430
797
  fontSize: "1.15rem",
@@ -432,28 +799,92 @@ var styles3 = {
432
799
  letterSpacing: "0.02em",
433
800
  margin: 0
434
801
  },
435
- subtitle: {
436
- fontSize: "0.85rem",
802
+ subtitle: { fontSize: "0.85rem", color: "var(--rs-muted, #6b7280)", marginTop: "0.25rem" },
803
+ scaleCaption: {
804
+ fontSize: "0.8rem",
805
+ fontStyle: "italic",
437
806
  color: "var(--rs-muted, #6b7280)",
438
807
  marginTop: "0.25rem"
439
808
  },
440
- table: {
441
- width: "100%",
442
- borderCollapse: "collapse",
443
- fontSize: "0.875rem"
809
+ slicers: {
810
+ display: "flex",
811
+ flexWrap: "wrap",
812
+ gap: "0.5rem",
813
+ margin: "0 0 0.75rem"
814
+ },
815
+ slicer: {
816
+ display: "inline-flex",
817
+ gap: "0.4rem",
818
+ alignItems: "baseline",
819
+ border: "1px solid var(--rs-border, #e5e7eb)",
820
+ borderRadius: "6px",
821
+ padding: "0.2rem 0.55rem",
822
+ fontSize: "0.78rem",
823
+ background: "var(--rs-slicer-bg, #f9fafb)"
824
+ },
825
+ slicerLabel: {
826
+ color: "var(--rs-muted, #6b7280)",
827
+ textTransform: "uppercase",
828
+ letterSpacing: "0.03em",
829
+ fontSize: "0.68rem"
830
+ },
831
+ slicerValue: { fontWeight: 600 },
832
+ controls: {
833
+ display: "flex",
834
+ alignItems: "center",
835
+ gap: "0.5rem",
836
+ margin: "0 0 0.9rem",
837
+ fontSize: "0.8rem"
838
+ },
839
+ controlLabel: { color: "var(--rs-muted, #6b7280)" },
840
+ segmented: {
841
+ display: "inline-flex",
842
+ border: "1px solid var(--rs-border, #e5e7eb)",
843
+ borderRadius: "7px",
844
+ overflow: "hidden"
845
+ },
846
+ segment: {
847
+ border: "none",
848
+ padding: "0.25rem 0.7rem",
849
+ fontSize: "0.78rem",
850
+ cursor: "pointer",
851
+ background: "transparent",
852
+ color: "var(--rs-text, #111827)"
853
+ },
854
+ segmentActive: {
855
+ background: "var(--rs-primary-600, #2563eb)",
856
+ color: "#ffffff",
857
+ fontWeight: 600
444
858
  },
859
+ scroll: { overflowX: "auto" },
860
+ table: { width: "100%", borderCollapse: "collapse", fontSize: "0.875rem" },
445
861
  th: {
446
862
  borderBottom: "2px solid var(--rs-border-strong, #9ca3af)",
447
- padding: "0.5rem 0.75rem",
863
+ padding: "0.4rem 0.75rem",
448
864
  fontWeight: 600,
449
865
  color: "var(--rs-muted, #6b7280)",
450
866
  fontSize: "0.8rem"
451
867
  },
452
- thConcept: { textAlign: "left" },
453
- thNum: { textAlign: "right", whiteSpace: "nowrap" },
454
- labelCell: {
868
+ thGroup: {
869
+ borderBottom: "1px solid var(--rs-border, #e5e7eb)",
455
870
  padding: "0.3rem 0.75rem",
456
- whiteSpace: "normal"
871
+ fontWeight: 600,
872
+ color: "var(--rs-muted, #6b7280)",
873
+ fontSize: "0.8rem",
874
+ textAlign: "center"
875
+ },
876
+ // A floor on the concept column so a wide table (many member/period columns)
877
+ // scrolls horizontally rather than squeezing labels down to vertical text.
878
+ thConcept: { textAlign: "left", verticalAlign: "bottom", minWidth: "18rem" },
879
+ thNum: { textAlign: "right", whiteSpace: "nowrap" },
880
+ labelCell: { padding: "0.3rem 0.75rem", whiteSpace: "normal", minWidth: "18rem" },
881
+ headerRowLabel: {
882
+ padding: "0.4rem 0.75rem",
883
+ fontWeight: 700,
884
+ fontSize: "0.8rem",
885
+ textTransform: "uppercase",
886
+ letterSpacing: "0.02em",
887
+ color: "var(--rs-muted, #6b7280)"
457
888
  },
458
889
  numCell: {
459
890
  padding: "0.3rem 0.75rem",
@@ -462,10 +893,7 @@ var styles3 = {
462
893
  fontFamily: "var(--rs-font-mono, ui-monospace, SFMono-Regular, monospace)",
463
894
  fontVariantNumeric: "tabular-nums"
464
895
  },
465
- textBlockCell: {
466
- padding: "0.75rem",
467
- borderTop: "1px solid var(--rs-border, #e5e7eb)"
468
- },
896
+ textBlockCell: { padding: "0.75rem", borderTop: "1px solid var(--rs-border, #e5e7eb)" },
469
897
  textBlockLabel: {
470
898
  fontSize: "0.7rem",
471
899
  textTransform: "uppercase",
@@ -475,35 +903,112 @@ var styles3 = {
475
903
  marginBottom: "0.5rem"
476
904
  }
477
905
  };
906
+ function cellSymbol(unit) {
907
+ return unit?.symbol ?? currencySymbolFor(unit?.measure) ?? "$";
908
+ }
478
909
  function StatementTable({
479
- statement,
910
+ table,
911
+ units,
480
912
  onCellClick,
481
- currencySymbol = "$",
482
- selected = null
913
+ selected = null,
914
+ dimensionPlacement,
915
+ onDimensionPlacementChange
483
916
  }) {
484
- const { columns, rows } = statement;
485
- const periodCaption = columns.length === 1 ? formatPeriod(columns[0].period) : null;
917
+ const { columns, rows, columnHeaders, scale } = table;
918
+ const totalCols = columns.length;
919
+ const dimensional = table.config.rows.some((k) => k.startsWith("dim:")) || table.config.columns.some((k) => k.startsWith("dim:"));
920
+ const showToggle = onDimensionPlacementChange != null && dimensional;
486
921
  return /* @__PURE__ */ jsxs("section", { style: styles3.wrap, children: [
487
922
  /* @__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
923
+ /* @__PURE__ */ jsx("h3", { style: styles3.title, children: table.title }),
924
+ table.structureName ? /* @__PURE__ */ jsx("div", { style: styles3.subtitle, children: table.structureName }) : null,
925
+ scale.caption ? /* @__PURE__ */ jsx("div", { style: styles3.scaleCaption, children: scale.caption }) : null
491
926
  ] }),
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
- ] }) }),
927
+ table.slicers.length ? /* @__PURE__ */ jsx("div", { style: styles3.slicers, children: table.slicers.map((s) => /* @__PURE__ */ jsxs("div", { style: styles3.slicer, children: [
928
+ /* @__PURE__ */ jsx("span", { style: styles3.slicerLabel, children: s.label }),
929
+ /* @__PURE__ */ jsx("span", { style: styles3.slicerValue, children: s.valueLabel })
930
+ ] }, s.aspect)) }) : null,
931
+ showToggle ? /* @__PURE__ */ jsxs("div", { style: styles3.controls, children: [
932
+ /* @__PURE__ */ jsx("span", { style: styles3.controlLabel, children: "Dimensions" }),
933
+ /* @__PURE__ */ jsx("div", { style: styles3.segmented, role: "group", "aria-label": "Dimension placement", children: ["rows", "columns"].map((placement) => /* @__PURE__ */ jsx(
934
+ "button",
935
+ {
936
+ type: "button",
937
+ onClick: () => onDimensionPlacementChange?.(placement),
938
+ "aria-pressed": dimensionPlacement === placement,
939
+ style: {
940
+ ...styles3.segment,
941
+ ...dimensionPlacement === placement ? styles3.segmentActive : {}
942
+ },
943
+ children: placement === "rows" ? "As rows" : "As columns"
944
+ },
945
+ placement
946
+ )) })
947
+ ] }) : null,
948
+ /* @__PURE__ */ jsx("div", { style: styles3.scroll, children: /* @__PURE__ */ jsxs("table", { style: styles3.table, children: [
949
+ /* @__PURE__ */ jsx("thead", { children: columnHeaders.map((level, levelIndex) => {
950
+ const isLast = levelIndex === columnHeaders.length - 1;
951
+ let leaf = 0;
952
+ return /* @__PURE__ */ jsxs("tr", { children: [
953
+ levelIndex === 0 ? /* @__PURE__ */ jsx(
954
+ "th",
955
+ {
956
+ rowSpan: columnHeaders.length,
957
+ style: { ...styles3.th, ...styles3.thConcept },
958
+ "aria-label": "Concept"
959
+ }
960
+ ) : null,
961
+ level.map((h, i) => {
962
+ const key = `${levelIndex}-${i}-${leaf}`;
963
+ leaf += h.span;
964
+ return /* @__PURE__ */ jsx(
965
+ "th",
966
+ {
967
+ colSpan: h.span,
968
+ style: isLast ? { ...styles3.th, ...styles3.thNum } : styles3.thGroup,
969
+ children: h.label
970
+ },
971
+ key
972
+ );
973
+ })
974
+ ] }, levelIndex);
975
+ }) }),
497
976
  /* @__PURE__ */ jsx("tbody", { children: rows.map((row) => {
977
+ if (row.header) {
978
+ return /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
979
+ "td",
980
+ {
981
+ colSpan: totalCols + 1,
982
+ style: {
983
+ ...styles3.headerRowLabel,
984
+ paddingLeft: `${row.depth * 20 + 12}px`
985
+ },
986
+ title: row.element.qname,
987
+ children: row.element.label
988
+ }
989
+ ) }, row.key);
990
+ }
498
991
  const textBlock = row.cells.find(
499
992
  (c) => c.value === null && isExternalFactUrl(c.textValue)
500
993
  );
501
994
  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 }),
995
+ return /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", { colSpan: totalCols + 1, style: styles3.textBlockCell, children: [
996
+ /* @__PURE__ */ jsx("div", { style: styles3.textBlockLabel, title: row.element.qname, children: rowLabel(row) }),
504
997
  /* @__PURE__ */ jsx(ExternalTextBlock, { url: textBlock.textValue })
505
- ] }) }, row.element.id);
998
+ ] }) }, row.key);
999
+ }
1000
+ if (row.element.itemType === "textBlock") {
1001
+ const inline = row.cells.find(
1002
+ (c) => c.value === null && c.textValue != null && c.textValue !== "" && !isExternalFactUrl(c.textValue)
1003
+ );
1004
+ if (inline?.textValue) {
1005
+ return /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", { colSpan: totalCols + 1, style: styles3.textBlockCell, children: [
1006
+ /* @__PURE__ */ jsx("div", { style: styles3.textBlockLabel, title: row.element.qname, children: rowLabel(row) }),
1007
+ /* @__PURE__ */ jsx(InlineTextBlock, { html: inline.textValue })
1008
+ ] }) }, row.key);
1009
+ }
506
1010
  }
1011
+ const kind = numericKindOf(row.element);
507
1012
  const isGrandTotal = row.isSubtotal && row.depth === 0;
508
1013
  const allZeroOrNull = row.cells.every((c) => c.value === null || c.value === 0);
509
1014
  const rowStyle = {
@@ -517,11 +1022,14 @@ function StatementTable({
517
1022
  borderBottom: isGrandTotal ? "3px double var(--rs-text, #111827)" : void 0
518
1023
  };
519
1024
  return /* @__PURE__ */ jsxs("tr", { style: rowStyle, children: [
520
- /* @__PURE__ */ jsx("td", { style: labelStyle, title: row.element.qname, children: row.element.label }),
1025
+ /* @__PURE__ */ jsx("td", { style: labelStyle, title: row.element.qname, children: rowLabel(row) }),
521
1026
  row.cells.map((cell, i) => {
522
- const isSelected = selected !== null && selected.elementId === row.element.id && selected.columnIndex === i;
1027
+ const isSelected = selected !== null && selected.rowKey === row.key && selected.columnIndex === i;
523
1028
  const clickable = onCellClick && cell.fact !== null;
524
1029
  const isText = cell.value === null && cell.textValue != null && cell.textValue !== "";
1030
+ const duration = isText ? humanizeDuration(cell.textValue) : null;
1031
+ const isProse = isText && !duration;
1032
+ const unit = cell.fact?.unit && units ? units[cell.fact.unit] : void 0;
525
1033
  const numStyle = {
526
1034
  ...styles3.numCell,
527
1035
  fontWeight: row.isSubtotal ? 600 : 400,
@@ -529,27 +1037,36 @@ function StatementTable({
529
1037
  cursor: clickable ? "pointer" : "default",
530
1038
  backgroundColor: isSelected ? "var(--rs-selected-bg, #dbeafe)" : void 0,
531
1039
  color: !isText && !row.isSubtotal && (cell.value === null || cell.value === 0) ? "var(--rs-muted, #9ca3af)" : void 0,
532
- ...isText ? {
1040
+ ...isProse ? {
533
1041
  textAlign: "left",
534
1042
  whiteSpace: "normal",
535
1043
  fontFamily: "var(--rs-font-sans, ui-sans-serif, system-ui, sans-serif)"
536
1044
  } : {}
537
1045
  };
1046
+ const display = isText ? duration ?? cell.textValue : cell.fact === null ? "" : cell.value === 0 ? "\u2014" : formatValue(cell.value, {
1047
+ numericKind: kind,
1048
+ symbol: cellSymbol(unit),
1049
+ scaleFactor: scale.factor
1050
+ });
538
1051
  return /* @__PURE__ */ jsx(
539
1052
  "td",
540
1053
  {
541
1054
  style: numStyle,
542
1055
  onClick: clickable ? () => onCellClick?.(row, i) : void 0,
543
- children: isText ? cell.textValue : formatMoney(cell.value, { currencySymbol })
1056
+ children: display
544
1057
  },
545
1058
  columns[i].key
546
1059
  );
547
1060
  })
548
- ] }, row.element.id);
1061
+ ] }, row.key);
549
1062
  }) })
550
- ] })
1063
+ ] }) })
551
1064
  ] });
552
1065
  }
1066
+ function rowLabel(row) {
1067
+ if (!row.members.length) return row.element.label;
1068
+ return row.members.map((m) => m.memberLabel).join(" \xB7 ");
1069
+ }
553
1070
  var styles4 = {
554
1071
  layout: {
555
1072
  display: "grid",
@@ -573,11 +1090,19 @@ var styles4 = {
573
1090
  },
574
1091
  panel: { position: "sticky", top: "1rem" }
575
1092
  };
576
- function ReportView({ report, currencySymbol = "$", inspect = true }) {
577
- const statements = useMemo(() => buildStatements(report), [report]);
1093
+ function ReportView({ report, inspect = true, dimensionAxis = "rows" }) {
1094
+ const [dimAxis, setDimAxis] = useState(dimensionAxis);
578
1095
  const [selection, setSelection] = useState(null);
1096
+ const statements = useMemo(
1097
+ () => buildPivots(report, (_ib, cfg) => pivotDimensionsOn(cfg, dimAxis)),
1098
+ [report, dimAxis]
1099
+ );
579
1100
  const selected = selection !== null ? statements[selection.statementIndex] : null;
580
- const selectedRow = selected?.rows.find((r) => r.element.id === selection?.elementId) ?? null;
1101
+ const selectedRow = selected?.rows.find((r) => r.key === selection?.rowKey) ?? null;
1102
+ const setPlacement = (placement) => {
1103
+ setSelection(null);
1104
+ setDimAxis(placement);
1105
+ };
581
1106
  return /* @__PURE__ */ jsxs("div", { children: [
582
1107
  report.entity ? /* @__PURE__ */ jsx("header", { style: styles4.entity, children: /* @__PURE__ */ jsx("h2", { style: styles4.entityName, children: report.entity.name }) }) : null,
583
1108
  /* @__PURE__ */ jsxs(
@@ -591,17 +1116,12 @@ function ReportView({ report, currencySymbol = "$", inspect = true }) {
591
1116
  /* @__PURE__ */ jsx("div", { children: statements.map((statement, statementIndex) => /* @__PURE__ */ jsx(
592
1117
  StatementTable,
593
1118
  {
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
1119
+ table: statement,
1120
+ units: report.units,
1121
+ dimensionPlacement: dimAxis,
1122
+ onDimensionPlacementChange: setPlacement,
1123
+ selected: selection && selection.statementIndex === statementIndex ? { rowKey: selection.rowKey, columnIndex: selection.columnIndex } : null,
1124
+ onCellClick: inspect ? (row, columnIndex) => setSelection({ statementIndex, rowKey: row.key, columnIndex }) : void 0
605
1125
  },
606
1126
  statement.ib.id
607
1127
  )) }),
@@ -609,10 +1129,9 @@ function ReportView({ report, currencySymbol = "$", inspect = true }) {
609
1129
  FactInspector,
610
1130
  {
611
1131
  report,
612
- statement: selected,
1132
+ table: selected,
613
1133
  row: selectedRow,
614
1134
  columnIndex: selection.columnIndex,
615
- currencySymbol,
616
1135
  onClose: () => setSelection(null)
617
1136
  }
618
1137
  ) }) : null
@@ -622,6 +1141,6 @@ function ReportView({ report, currencySymbol = "$", inspect = true }) {
622
1141
  ] });
623
1142
  }
624
1143
 
625
- export { ExternalTextBlock, FactInspector, ReportView, StatementTable, buildStatements, calcSubtotals, footCheck, formatDate, formatMoney, formatPeriod, presentationOrder, reportSections, sliceReportSection };
1144
+ export { ExternalTextBlock, FactInspector, ReportView, StatementTable, buildPivot, buildPivots, calcSubtotals, defaultPivotConfig, footCheck, pivotDimensionsOn, presentationOrder, reportSections, sliceReportSection };
626
1145
  //# sourceMappingURL=index.js.map
627
1146
  //# sourceMappingURL=index.js.map