@bicharts/chart-host 0.6.75 → 0.6.77

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.
@@ -0,0 +1,614 @@
1
+ /*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
+ import {
3
+ L,
4
+ Qe,
5
+ Qn,
6
+ Zn,
7
+ le,
8
+ qe
9
+ } from "./chunk-H2P63UMG.mjs";
10
+
11
+ // src/sourceDateFormat.ts
12
+ function formatSourceDate(raw, opts = {}) {
13
+ const d = toDate(raw);
14
+ if (!d) return null;
15
+ const dayFrame = L(d);
16
+ const frame = dayFrame ?? (opts.dialect === "dotnet" ? "local" : "utc");
17
+ const p = partsIn(d, frame);
18
+ const format = cleanFormat(opts.format, opts.dialect);
19
+ if (format && opts.dialect) {
20
+ const rendered = renderFormat(format, p, opts.dialect, opts.culture);
21
+ if (rendered) return rendered;
22
+ }
23
+ return localeDate(p, !!dayFrame, opts.culture);
24
+ }
25
+ function formatSourceDateFor(raw, columnName, formats, culture) {
26
+ return formatSourceDate(raw, {
27
+ format: formats?.byColumn?.[columnName] ?? null,
28
+ dialect: formats?.dialect,
29
+ culture
30
+ });
31
+ }
32
+ var ISO_LIKE_RE = /^\s*\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?\s*$/;
33
+ function toDate(raw) {
34
+ if (raw instanceof Date) return isNaN(raw.getTime()) ? null : raw;
35
+ if (typeof raw !== "string" || !ISO_LIKE_RE.test(raw)) return null;
36
+ const d = le(raw.trim());
37
+ return d && !isNaN(d.getTime()) ? d : null;
38
+ }
39
+ function partsIn(d, frame) {
40
+ return frame === "utc" ? {
41
+ year: d.getUTCFullYear(),
42
+ month: d.getUTCMonth() + 1,
43
+ day: d.getUTCDate(),
44
+ hours: d.getUTCHours(),
45
+ minutes: d.getUTCMinutes(),
46
+ seconds: d.getUTCSeconds(),
47
+ ms: d.getUTCMilliseconds(),
48
+ weekday: d.getUTCDay()
49
+ } : {
50
+ year: d.getFullYear(),
51
+ month: d.getMonth() + 1,
52
+ day: d.getDate(),
53
+ hours: d.getHours(),
54
+ minutes: d.getMinutes(),
55
+ seconds: d.getSeconds(),
56
+ ms: d.getMilliseconds(),
57
+ weekday: d.getDay()
58
+ };
59
+ }
60
+ function utcAnchor(p) {
61
+ const d = new Date(Date.UTC(p.year, p.month - 1, p.day, p.hours, p.minutes, p.seconds, p.ms));
62
+ if (p.year >= 0 && p.year < 100) d.setUTCFullYear(p.year);
63
+ return d;
64
+ }
65
+ function localeDate(p, wholeDay, culture) {
66
+ const anchor = utcAnchor(p);
67
+ try {
68
+ const o = { dateStyle: "medium", timeZone: "UTC" };
69
+ if (!wholeDay) o.timeStyle = p.seconds || p.ms ? "medium" : "short";
70
+ return new Intl.DateTimeFormat(culture || void 0, o).format(anchor);
71
+ } catch {
72
+ const iso = anchor.toISOString();
73
+ return wholeDay ? iso.slice(0, 10) : `${iso.slice(0, 10)} ${iso.slice(11, 19)}`;
74
+ }
75
+ }
76
+ function cleanFormat(raw, dialect) {
77
+ if (!raw || !dialect) return null;
78
+ let s = firstSection(String(raw), dialect);
79
+ if (dialect === "excel") {
80
+ s = s.replace(/\[(h{1,2}|m{1,2}|s{1,2})\]/gi, "$1").replace(/\[[^\]]*\]/g, "");
81
+ }
82
+ if (!s.trim() || /^general$/i.test(s.trim())) return null;
83
+ return hasDateToken(s, dialect) ? s : null;
84
+ }
85
+ function isExcelDateFormat(raw) {
86
+ if (!raw) return false;
87
+ const bare = stripLiterals(firstSection(String(raw), "excel"), "excel");
88
+ if (/\[(h+|m+|s+)\]/i.test(bare)) return false;
89
+ const tokens = bare.replace(/\[[^\]]*\]/g, "").trim();
90
+ if (!tokens || /^general$/i.test(tokens)) return false;
91
+ return /[ymdhs]/i.test(tokens);
92
+ }
93
+ function firstSection(s, dialect) {
94
+ let out = "";
95
+ let quote = "";
96
+ for (let i = 0; i < s.length; i++) {
97
+ const ch = s[i];
98
+ if (quote) {
99
+ out += ch;
100
+ if (ch === quote) quote = "";
101
+ continue;
102
+ }
103
+ if (ch === '"' || dialect === "dotnet" && ch === "'") {
104
+ quote = ch;
105
+ out += ch;
106
+ continue;
107
+ }
108
+ if (ch === "\\") {
109
+ out += ch + (s[i + 1] ?? "");
110
+ i++;
111
+ continue;
112
+ }
113
+ if (ch === ";") break;
114
+ out += ch;
115
+ }
116
+ return out;
117
+ }
118
+ function hasDateToken(s, dialect) {
119
+ const stripped = stripLiterals(s, dialect);
120
+ return dialect === "excel" ? /[ymdhs]/i.test(stripped) : /[yMdHhmsft]/.test(stripped);
121
+ }
122
+ function stripLiterals(s, dialect) {
123
+ let out = "";
124
+ for (let i = 0; i < s.length; i++) {
125
+ const ch = s[i];
126
+ if (ch === "\\" || dialect === "dotnet" && ch === "%") {
127
+ i++;
128
+ continue;
129
+ }
130
+ if (dialect === "excel" && (ch === "_" || ch === "*")) {
131
+ i++;
132
+ continue;
133
+ }
134
+ if (ch === '"' || dialect === "dotnet" && ch === "'") {
135
+ const end = s.indexOf(ch, i + 1);
136
+ i = end < 0 ? s.length : end;
137
+ continue;
138
+ }
139
+ out += ch;
140
+ }
141
+ return out;
142
+ }
143
+ var EXCEL_TOKENS = [
144
+ [/^am\/pm/i, "meridiemLong"],
145
+ [/^a\/p/i, "meridiemShort"],
146
+ [/^y{3,}/i, "year4"],
147
+ [/^y{1,2}/i, "year2"],
148
+ [/^m{5,}/i, "monthNarrow"],
149
+ [/^m{4}/i, "monthLong"],
150
+ [/^m{3}/i, "monthShort"],
151
+ [/^m{2}/i, "mAmbiguous2"],
152
+ [/^m/i, "mAmbiguous1"],
153
+ [/^d{4,}/i, "weekdayLong"],
154
+ [/^d{3}/i, "weekdayShort"],
155
+ [/^d{2}/i, "day2"],
156
+ [/^d/i, "day1"],
157
+ [/^h{2,}/i, "hour2"],
158
+ [/^h/i, "hour1"],
159
+ [/^s{2,}/i, "second2"],
160
+ [/^s/i, "second1"]
161
+ ];
162
+ var DOTNET_TOKENS = [
163
+ [/^y{3,}/, "year4"],
164
+ [/^y{1,2}/, "year2"],
165
+ [/^M{4,}/, "monthLong"],
166
+ [/^M{3}/, "monthShort"],
167
+ [/^M{2}/, "month2"],
168
+ [/^M/, "month1"],
169
+ [/^d{4,}/, "weekdayLong"],
170
+ [/^d{3}/, "weekdayShort"],
171
+ [/^d{2}/, "day2"],
172
+ [/^d/, "day1"],
173
+ [/^H{2,}/, "hour2"],
174
+ [/^H/, "hour1"],
175
+ [/^h{2,}/, "hour2"],
176
+ [/^h/, "hour1"],
177
+ [/^m{2,}/, "minute2"],
178
+ [/^m/, "minute1"],
179
+ [/^s{2,}/, "second2"],
180
+ [/^s/, "second1"],
181
+ [/^f+/, "fraction"],
182
+ [/^tt/, "meridiemLong"],
183
+ [/^t/, "meridiemShort"]
184
+ ];
185
+ function tokenize(s, dialect) {
186
+ const table = dialect === "excel" ? EXCEL_TOKENS : DOTNET_TOKENS;
187
+ const out = [];
188
+ let i = 0;
189
+ while (i < s.length) {
190
+ const ch = s[i];
191
+ if (ch === "\\" || dialect === "dotnet" && ch === "%") {
192
+ if (i + 1 < s.length) out.push({ kind: "literal", text: s[i + 1] });
193
+ i += 2;
194
+ continue;
195
+ }
196
+ if (ch === '"' || dialect === "dotnet" && ch === "'") {
197
+ const end = s.indexOf(ch, i + 1);
198
+ out.push({ kind: "literal", text: end < 0 ? s.slice(i + 1) : s.slice(i + 1, end) });
199
+ i = end < 0 ? s.length : end + 1;
200
+ continue;
201
+ }
202
+ const rest = s.slice(i);
203
+ let matched = false;
204
+ for (const [re, kind] of table) {
205
+ const m = re.exec(rest);
206
+ if (m) {
207
+ out.push({ kind });
208
+ i += m[0].length;
209
+ matched = true;
210
+ break;
211
+ }
212
+ }
213
+ if (!matched) {
214
+ out.push({ kind: "literal", text: ch });
215
+ i++;
216
+ }
217
+ }
218
+ return out;
219
+ }
220
+ function resolveAmbiguousM(tokens) {
221
+ const isHour = (t) => t.kind === "hour1" || t.kind === "hour2";
222
+ const isSecond = (t) => t.kind === "second1" || t.kind === "second2";
223
+ for (let i = 0; i < tokens.length; i++) {
224
+ const t = tokens[i];
225
+ if (t.kind !== "mAmbiguous1" && t.kind !== "mAmbiguous2") continue;
226
+ const two = t.kind === "mAmbiguous2";
227
+ let minute = false;
228
+ for (let j = i - 1; j >= 0; j--) {
229
+ if (tokens[j].kind === "literal") continue;
230
+ minute = isHour(tokens[j]);
231
+ break;
232
+ }
233
+ if (!minute) {
234
+ for (let j = i + 1; j < tokens.length; j++) {
235
+ if (tokens[j].kind === "literal") continue;
236
+ minute = isSecond(tokens[j]);
237
+ break;
238
+ }
239
+ }
240
+ t.kind = minute ? two ? "minute2" : "minute1" : two ? "month2" : "month1";
241
+ }
242
+ }
243
+ var EN_MONTHS_LONG = [
244
+ "January",
245
+ "February",
246
+ "March",
247
+ "April",
248
+ "May",
249
+ "June",
250
+ "July",
251
+ "August",
252
+ "September",
253
+ "October",
254
+ "November",
255
+ "December"
256
+ ];
257
+ var EN_MONTHS_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
258
+ var EN_DAYS_LONG = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
259
+ var EN_DAYS_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
260
+ function name(p, kind, width, culture) {
261
+ try {
262
+ const opts = kind === "month" ? { month: width, timeZone: "UTC" } : { weekday: width === "narrow" ? "short" : width, timeZone: "UTC" };
263
+ return new Intl.DateTimeFormat(culture || void 0, opts).format(utcAnchor(p));
264
+ } catch {
265
+ if (kind === "month") {
266
+ const long = EN_MONTHS_LONG[p.month - 1] ?? "";
267
+ if (width === "long") return long;
268
+ return width === "narrow" ? long.slice(0, 1) : EN_MONTHS_SHORT[p.month - 1] ?? "";
269
+ }
270
+ return (width === "long" ? EN_DAYS_LONG[p.weekday] : EN_DAYS_SHORT[p.weekday]) ?? "";
271
+ }
272
+ }
273
+ var p2 = (n) => (n < 10 ? "0" : "") + n;
274
+ function renderFormat(format, p, dialect, culture) {
275
+ const tokens = tokenize(format, dialect);
276
+ if (dialect === "excel") resolveAmbiguousM(tokens);
277
+ const twelve = tokens.some((t) => t.kind === "meridiemLong" || t.kind === "meridiemShort");
278
+ const hour12 = () => {
279
+ const h = p.hours % 12;
280
+ return h === 0 ? 12 : h;
281
+ };
282
+ let out = "";
283
+ let sawField = false;
284
+ for (const t of tokens) {
285
+ switch (t.kind) {
286
+ case "literal":
287
+ out += t.text ?? "";
288
+ break;
289
+ case "year4":
290
+ out += String(p.year);
291
+ sawField = true;
292
+ break;
293
+ case "year2":
294
+ out += p2((p.year % 100 + 100) % 100);
295
+ sawField = true;
296
+ break;
297
+ case "monthLong":
298
+ out += name(p, "month", "long", culture);
299
+ sawField = true;
300
+ break;
301
+ case "monthShort":
302
+ out += name(p, "month", "short", culture);
303
+ sawField = true;
304
+ break;
305
+ case "monthNarrow":
306
+ out += name(p, "month", "long", culture).slice(0, 1);
307
+ sawField = true;
308
+ break;
309
+ case "month2":
310
+ out += p2(p.month);
311
+ sawField = true;
312
+ break;
313
+ case "month1":
314
+ out += String(p.month);
315
+ sawField = true;
316
+ break;
317
+ case "weekdayLong":
318
+ out += name(p, "weekday", "long", culture);
319
+ sawField = true;
320
+ break;
321
+ case "weekdayShort":
322
+ out += name(p, "weekday", "short", culture);
323
+ sawField = true;
324
+ break;
325
+ case "day2":
326
+ out += p2(p.day);
327
+ sawField = true;
328
+ break;
329
+ case "day1":
330
+ out += String(p.day);
331
+ sawField = true;
332
+ break;
333
+ case "hour2":
334
+ out += p2(twelve ? hour12() : p.hours);
335
+ sawField = true;
336
+ break;
337
+ case "hour1":
338
+ out += String(twelve ? hour12() : p.hours);
339
+ sawField = true;
340
+ break;
341
+ case "minute2":
342
+ out += p2(p.minutes);
343
+ sawField = true;
344
+ break;
345
+ case "minute1":
346
+ out += String(p.minutes);
347
+ sawField = true;
348
+ break;
349
+ case "second2":
350
+ out += p2(p.seconds);
351
+ sawField = true;
352
+ break;
353
+ case "second1":
354
+ out += String(p.seconds);
355
+ sawField = true;
356
+ break;
357
+ case "fraction":
358
+ out += String(p.ms).padStart(3, "0");
359
+ sawField = true;
360
+ break;
361
+ case "meridiemLong":
362
+ out += p.hours < 12 ? "AM" : "PM";
363
+ sawField = true;
364
+ break;
365
+ case "meridiemShort":
366
+ out += p.hours < 12 ? "A" : "P";
367
+ sawField = true;
368
+ break;
369
+ }
370
+ }
371
+ return sawField && out.trim() ? out : null;
372
+ }
373
+
374
+ // src/selectionCard.ts
375
+ var SYNTHETIC_PREFIX = "__";
376
+ var NUMERIC_DATATYPE = /int|double|decimal|single|float|number|currency|money/i;
377
+ function normaliseAggregation(raw) {
378
+ const s = String(raw ?? "").trim().toLowerCase();
379
+ if (!s) return "sum";
380
+ if (/^(avg|average|mean)$/.test(s)) return "average";
381
+ if (/^(min|minimum)$/.test(s)) return "min";
382
+ if (/^(max|maximum)$/.test(s)) return "max";
383
+ if (/^(count|countrows)$/.test(s)) return "count";
384
+ if (/^(median|med)$/.test(s)) return "median";
385
+ if (/^(distinctcount|dcount|countdistinct|uniques?)$/.test(s)) return "distinctcount";
386
+ return "sum";
387
+ }
388
+ function aggLabel(agg) {
389
+ switch (agg) {
390
+ case "average":
391
+ return "Average";
392
+ case "min":
393
+ return "Min";
394
+ case "max":
395
+ return "Max";
396
+ case "count":
397
+ return "Count";
398
+ case "median":
399
+ return "Median";
400
+ case "distinctcount":
401
+ return "Distinct";
402
+ default:
403
+ return "Sum";
404
+ }
405
+ }
406
+ function measureLineLabel(agg, name2) {
407
+ const col = String(name2 ?? "");
408
+ const prefix = aggLabel(agg) + " of ";
409
+ return col.trim().toLowerCase().startsWith(prefix.toLowerCase()) ? col.trim() : prefix + col;
410
+ }
411
+ function isSynthetic(name2) {
412
+ return String(name2 ?? "").startsWith(SYNTHETIC_PREFIX);
413
+ }
414
+ function isMeasureColumn(col) {
415
+ if (!col || isSynthetic(col.name)) return false;
416
+ if (col.isMeasure === true) return true;
417
+ return NUMERIC_DATATYPE.test(String(col.dataType ?? ""));
418
+ }
419
+ function isRealColumn(col) {
420
+ return !!col && !isSynthetic(col.name);
421
+ }
422
+ function isSerialisedDateColumn(col) {
423
+ return /^datetime/i.test(String(col?.dataType ?? ""));
424
+ }
425
+ function displayText(col, raw, opts) {
426
+ if (raw == null || raw === "") return "(blank)";
427
+ if (isSerialisedDateColumn(col)) {
428
+ const shown = formatSourceDate(raw, {
429
+ format: opts.sourceFormats?.byColumn?.[String(col?.name ?? "")] ?? null,
430
+ dialect: opts.sourceFormats?.dialect,
431
+ culture: opts.cultureCode
432
+ });
433
+ if (shown) return shown;
434
+ }
435
+ return String(raw);
436
+ }
437
+ function isSuppressed(col) {
438
+ return qe(col).suppressed;
439
+ }
440
+ function resolveAgg(col, requested, auto) {
441
+ const fallback = () => {
442
+ const d = Zn(col);
443
+ return d === "first" ? "count" : d;
444
+ };
445
+ if (auto) return fallback();
446
+ return Qe(col).indexOf(requested) >= 0 ? requested : fallback();
447
+ }
448
+ function toNumber(v) {
449
+ if (v == null || v === "") return null;
450
+ const n = typeof v === "number" ? v : Number(v);
451
+ return Number.isFinite(n) ? n : null;
452
+ }
453
+ function reduce(values, agg, distinctRaw) {
454
+ if (agg === "count") return values.length;
455
+ if (agg === "distinctcount") return new Set(distinctRaw.map((v) => v == null ? "\0" : String(v))).size;
456
+ if (!values.length) return null;
457
+ switch (agg) {
458
+ case "average":
459
+ return values.reduce((a, b) => a + b, 0) / values.length;
460
+ case "min":
461
+ return Math.min(...values);
462
+ case "max":
463
+ return Math.max(...values);
464
+ case "median": {
465
+ const s = [...values].sort((a, b) => a - b);
466
+ const m = s.length >> 1;
467
+ return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
468
+ }
469
+ default:
470
+ return values.reduce((a, b) => a + b, 0);
471
+ }
472
+ }
473
+ function fmtNumber(v, culture) {
474
+ try {
475
+ const abs = Math.abs(v);
476
+ const opts = abs >= 1e6 ? { notation: "compact", maximumFractionDigits: 1 } : { maximumFractionDigits: abs < 10 ? 2 : 0 };
477
+ return new Intl.NumberFormat(culture || void 0, opts).format(v);
478
+ } catch {
479
+ return String(Math.round(v * 100) / 100);
480
+ }
481
+ }
482
+ function computeSelectionCard(payload, selectedRowIdxs, opts = {}) {
483
+ const columns = payload?.columns ?? [];
484
+ const rows = payload?.rows ?? [];
485
+ if (!columns.length || !rows.length) return null;
486
+ const wanted = Array.from(new Set((selectedRowIdxs ?? []).filter((n) => Number.isInteger(n))));
487
+ if (!wanted.length) return null;
488
+ const rowIdxCol = columns.findIndex((c) => c?.name === "__rowIdx__");
489
+ let byIdx = null;
490
+ if (rowIdxCol >= 0) {
491
+ byIdx = /* @__PURE__ */ new Map();
492
+ for (let r = 0; r < rows.length; r++) {
493
+ const v = toNumber(rows[r]?.[rowIdxCol]);
494
+ if (v != null) byIdx.set(v, r);
495
+ }
496
+ }
497
+ const positions = [];
498
+ for (const idx of wanted) {
499
+ const pos = byIdx ? byIdx.get(idx) : idx;
500
+ if (pos != null && pos >= 0 && pos < rows.length) positions.push(pos);
501
+ }
502
+ if (!positions.length) return null;
503
+ const auto = String(opts.aggregation ?? "").trim().length === 0;
504
+ const agg = normaliseAggregation(opts.aggregation);
505
+ const maxMeasures = opts.maxMeasures ?? 4;
506
+ const maxHeaderValues = opts.maxHeaderValues ?? 3;
507
+ const maxDimensions = opts.maxDimensions ?? 2;
508
+ const dimIdx = columns.findIndex((c) => c && !isSynthetic(c.name) && !isMeasureColumn(c));
509
+ let header;
510
+ if (dimIdx >= 0) {
511
+ const seen = [];
512
+ const set = /* @__PURE__ */ new Set();
513
+ for (const p of positions) {
514
+ const s = displayText(columns[dimIdx], rows[p]?.[dimIdx], opts);
515
+ if (!set.has(s)) {
516
+ set.add(s);
517
+ seen.push(s);
518
+ }
519
+ if (seen.length > maxHeaderValues) break;
520
+ }
521
+ header = seen.length > maxHeaderValues ? `${positions.length} marks` : seen.join(", ");
522
+ } else {
523
+ header = `${positions.length} mark${positions.length === 1 ? "" : "s"}`;
524
+ }
525
+ const measureCols = [];
526
+ const suppressedColumns = [];
527
+ for (let c = 0; c < columns.length; c++) {
528
+ if (!isMeasureColumn(columns[c])) continue;
529
+ if (isSuppressed(columns[c])) {
530
+ suppressedColumns.push(String(columns[c]?.name ?? ""));
531
+ continue;
532
+ }
533
+ measureCols.push(c);
534
+ }
535
+ const shown = measureCols.slice(0, maxMeasures);
536
+ const lines = [];
537
+ for (const c of shown) {
538
+ const col = columns[c];
539
+ const colAgg = resolveAgg(col, agg, auto);
540
+ const selVals = [];
541
+ const selRaw = [];
542
+ for (const p of positions) {
543
+ const n = toNumber(rows[p]?.[c]);
544
+ selRaw.push(rows[p]?.[c]);
545
+ if (n != null) selVals.push(n);
546
+ }
547
+ const value = reduce(selVals, colAgg, selRaw);
548
+ let sharePct = null;
549
+ if (Qn(col, colAgg) && value != null) {
550
+ const allVals = [];
551
+ const allRaw = [];
552
+ for (let r = 0; r < rows.length; r++) {
553
+ const n = toNumber(rows[r]?.[c]);
554
+ allRaw.push(rows[r]?.[c]);
555
+ if (n != null) allVals.push(n);
556
+ }
557
+ const total = reduce(allVals, colAgg, allRaw);
558
+ if (total != null && total > 0) sharePct = value / total * 100;
559
+ }
560
+ lines.push({
561
+ column: String(col?.name ?? ""),
562
+ label: measureLineLabel(colAgg, String(col?.name ?? "")),
563
+ value,
564
+ valueText: value == null ? "\u2014" : fmtNumber(value, opts.cultureCode),
565
+ sharePct,
566
+ shareText: sharePct == null ? "" : `${sharePct < 0.1 ? "<0.1" : sharePct.toFixed(1)}% of total`,
567
+ aggregation: colAgg
568
+ });
569
+ }
570
+ const dimensionLines = [];
571
+ for (let c = 0; c < columns.length && dimensionLines.length < maxDimensions; c++) {
572
+ if (c === dimIdx) continue;
573
+ if (!isRealColumn(columns[c]) || isMeasureColumn(columns[c])) continue;
574
+ const name2 = String(columns[c]?.name ?? "");
575
+ const distinct = /* @__PURE__ */ new Set();
576
+ let firstText = "";
577
+ for (const p of positions) {
578
+ const raw = rows[p]?.[c];
579
+ const key = raw == null || raw === "" ? "(blank)" : String(raw);
580
+ if (!distinct.size) firstText = displayText(columns[c], raw, opts);
581
+ distinct.add(key);
582
+ }
583
+ if (!distinct.size) continue;
584
+ const single = distinct.size === 1;
585
+ dimensionLines.push({
586
+ column: name2,
587
+ label: single ? name2 : `Distinct ${name2}`,
588
+ value: single ? null : distinct.size,
589
+ valueText: single ? firstText : fmtNumber(distinct.size, opts.cultureCode),
590
+ sharePct: null,
591
+ shareText: "",
592
+ aggregation: single ? "first" : "distinctcount"
593
+ });
594
+ }
595
+ return {
596
+ header,
597
+ lines,
598
+ dimensionLines,
599
+ suppressedColumns,
600
+ rowsText: `${positions.length} of ${rows.length} rows`,
601
+ selectedRows: positions.length,
602
+ totalRows: rows.length,
603
+ hiddenMeasures: Math.max(0, measureCols.length - shown.length),
604
+ aggregation: agg
605
+ };
606
+ }
607
+
608
+ export {
609
+ formatSourceDate,
610
+ formatSourceDateFor,
611
+ isExcelDateFormat,
612
+ normaliseAggregation,
613
+ computeSelectionCard
614
+ };