@bicharts/chart-host 0.5.86 → 0.5.87

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.mjs CHANGED
@@ -15,7 +15,6 @@ import {
15
15
  AXIS_PIN_MAX_BAND_FRACTION,
16
16
  AXIS_PIN_MIN_LABELS,
17
17
  AXIS_PIN_TRACK_REACH_PX,
18
- Ae,
19
18
  BACKING_MAJORITY,
20
19
  COLOR_SCALE_SELF_CLAMP_PCT_DEFAULT,
21
20
  COLOR_SCALE_SELF_CLAMP_PCT_MAX,
@@ -24,7 +23,6 @@ import {
24
23
  CONTAINER_SLOT_INITIAL_XF_MARK,
25
24
  CONTAINER_SLOT_UI_STATE,
26
25
  CONTAINER_SLOT_XF_CLEAR,
27
- Ce,
28
26
  DARK_TEXT,
29
27
  DIM_OPACITY_DEFAULT,
30
28
  DIM_OPACITY_VAR,
@@ -34,6 +32,7 @@ import {
34
32
  GLYPH_SAMPLE_N,
35
33
  HOST_CONTAINER_CLASS,
36
34
  HOST_CONTRACT_VERSION,
35
+ It,
37
36
  L3,
38
37
  LABEL_CONTRAST_CAP,
39
38
  LABEL_CONTRAST_DONE_ATTR,
@@ -45,16 +44,17 @@ import {
45
44
  MAX_FRAME_GROW_FACTOR,
46
45
  MIN_CONTRAST,
47
46
  MIN_HIT_BAND_PX,
47
+ Me,
48
48
  PAGE_MATCH_TOLERANCE,
49
49
  PHANTOM_FRACTION,
50
50
  PILL_MIN_ALPHA,
51
51
  PILL_MIN_COVERAGE,
52
52
  PILL_OPAQUE_ALPHA,
53
53
  ROW_IDX_ATTR,
54
- Rt,
55
54
  SCROLL_SLACK_PX,
56
55
  SEASONAL_MARKERS_DEFAULT,
57
56
  SELECTION_ACTIVE_CLASS,
57
+ Te,
58
58
  VALUE_AXIS_BASELINE_DEFAULT,
59
59
  WHITE_TEXT_BG_LUM,
60
60
  XFILTER_REFRESH_EVENT,
@@ -93,6 +93,7 @@ import {
93
93
  measureContainerBoxes,
94
94
  needsScroll,
95
95
  noopViewStateProvider,
96
+ p,
96
97
  periodTickSuppressesFeedback,
97
98
  pillBacksGlyph,
98
99
  pinScrolledAxis,
@@ -108,9 +109,10 @@ import {
108
109
  stripEsmExports,
109
110
  svgInkReach,
110
111
  toRGBA,
112
+ u,
111
113
  unpinScrolledAxis,
112
114
  vt
113
- } from "./chunk-R4STRHXO.mjs";
115
+ } from "./chunk-NWMMNXH2.mjs";
114
116
  import "./chunk-A2GMXZP7.mjs";
115
117
  import "./chunk-GHODWOAL.mjs";
116
118
 
@@ -148,7 +150,7 @@ function planTrivialChart(data) {
148
150
  }
149
151
  if (dataCols.length !== 1) return null;
150
152
  const idx = dataCols[0];
151
- const name = columnName(allCols[idx]) || "Value";
153
+ const name2 = columnName(allCols[idx]) || "Value";
152
154
  const finish = (kind, reason, source) => ({
153
155
  kind,
154
156
  reason,
@@ -160,7 +162,7 @@ function planTrivialChart(data) {
160
162
  return finish(
161
163
  "card",
162
164
  "One row of one column holds a single value, so this is a card. No generation was needed and none was used.",
163
- cardSource(idx, name)
165
+ cardSource(idx, name2)
164
166
  );
165
167
  }
166
168
  if (isNumericColumn(rows, idx)) {
@@ -168,7 +170,7 @@ function planTrivialChart(data) {
168
170
  return finish(
169
171
  "histogram",
170
172
  "A single numeric column has one defensible chart: the distribution of its values. No generation was needed and none was used.",
171
- histogramSource(idx, name)
173
+ histogramSource(idx, name2)
172
174
  );
173
175
  }
174
176
  const distinct = /* @__PURE__ */ new Set();
@@ -181,7 +183,7 @@ function planTrivialChart(data) {
181
183
  return finish(
182
184
  "frequency-bar",
183
185
  "A single categorical column has one defensible chart: how often each value occurs. No generation was needed and none was used.",
184
- frequencySource(idx, name)
186
+ frequencySource(idx, name2)
185
187
  );
186
188
  }
187
189
  function compileTrivialSource(source) {
@@ -222,7 +224,7 @@ var PRELUDE = `
222
224
  }
223
225
  container.replaceChildren();
224
226
  `;
225
- function cardSource(idx, name) {
227
+ function cardSource(idx, name2) {
226
228
  return `function render(container, data, options) {${PRELUDE}
227
229
  var raw = rows[0] ? rows[0][${idx}] : null;
228
230
  var blank = (raw === null || raw === undefined || raw === '');
@@ -254,13 +256,13 @@ function cardSource(idx, name) {
254
256
  label.style.marginTop = '6px'; label.style.textAlign = 'center';
255
257
  label.style.maxWidth = '100%'; label.style.overflow = 'hidden';
256
258
  label.style.textOverflow = 'ellipsis'; label.style.whiteSpace = 'nowrap';
257
- label.textContent = ${lit(name)};
259
+ label.textContent = ${lit(name2)};
258
260
 
259
261
  root.appendChild(value); root.appendChild(label);
260
262
  container.appendChild(root);
261
263
  }`;
262
264
  }
263
- function frequencySource(idx, name) {
265
+ function frequencySource(idx, name2) {
264
266
  return `function render(container, data, options) {${PRELUDE}
265
267
  var counts = new Map();
266
268
  for (var r = 0; r < rows.length; r++) {
@@ -280,7 +282,7 @@ function frequencySource(idx, name) {
280
282
  var barH = Math.min(bandH - 3, 26);
281
283
 
282
284
  var title = svgEl('text', { x: 8, y: CF + 2, 'font-size': CF, 'font-weight': '700', fill: FG });
283
- title.textContent = ${lit(name)} + ' \\u2014 count of ' + fmt(rows.length) + ' rows';
285
+ title.textContent = ${lit(name2)} + ' \\u2014 count of ' + fmt(rows.length) + ' rows';
284
286
  svg.appendChild(title);
285
287
 
286
288
  entries.forEach(function (e, i) {
@@ -319,7 +321,7 @@ function frequencySource(idx, name) {
319
321
  container.appendChild(svg);
320
322
  }`;
321
323
  }
322
- function histogramSource(idx, name) {
324
+ function histogramSource(idx, name2) {
323
325
  return `function render(container, data, options) {${PRELUDE}
324
326
  var vals = [];
325
327
  for (var r = 0; r < rows.length; r++) {
@@ -355,7 +357,7 @@ function histogramSource(idx, name) {
355
357
  var bw = plotW / binCount;
356
358
 
357
359
  var title = svgEl('text', { x: padL, y: CF + 2, 'font-size': CF, 'font-weight': '700', fill: FG });
358
- title.textContent = ${lit(name)} + ' \\u2014 distribution of ' + fmt(vals.length) + ' values';
360
+ title.textContent = ${lit(name2)} + ' \\u2014 distribution of ' + fmt(vals.length) + ' values';
359
361
  svg.appendChild(title);
360
362
 
361
363
  bins.forEach(function (idxs, i) {
@@ -709,10 +711,10 @@ function normalizeFilterTerm(raw) {
709
711
  return raw.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/\s+/g, " ").trim();
710
712
  }
711
713
  var FILTER_MIN_TERM_CHARS = 2;
712
- function startsAWord(name, t) {
713
- for (let i = name.indexOf(t); i >= 0; i = name.indexOf(t, i + 1)) {
714
+ function startsAWord(name2, t) {
715
+ for (let i = name2.indexOf(t); i >= 0; i = name2.indexOf(t, i + 1)) {
714
716
  if (i === 0) return true;
715
- const prev = name.charCodeAt(i - 1);
717
+ const prev = name2.charCodeAt(i - 1);
716
718
  const alnum = prev >= 97 && prev <= 122 || prev >= 48 && prev <= 57;
717
719
  if (!alnum) return true;
718
720
  }
@@ -727,10 +729,10 @@ function filterQualifyRows(rows, term, read) {
727
729
  const byDesc = [];
728
730
  for (const row of all) {
729
731
  const f = read(row);
730
- const name = normalizeFilterTerm(f.name);
731
- if (name === "") continue;
732
- if (name.indexOf(t) >= 0) {
733
- (startsAWord(name, t) ? atWordStart : midWord).push(row);
732
+ const name2 = normalizeFilterTerm(f.name);
733
+ if (name2 === "") continue;
734
+ if (name2.indexOf(t) >= 0) {
735
+ (startsAWord(name2, t) ? atWordStart : midWord).push(row);
734
736
  continue;
735
737
  }
736
738
  if (normalizeFilterTerm(f.description).indexOf(t) >= 0) byDesc.push(row);
@@ -820,6 +822,357 @@ function inlineFilterGate(rowCount) {
820
822
  }
821
823
  var INLINE_FILTER_MIN_ROWS = 8;
822
824
 
825
+ // src/sourceDateFormat.ts
826
+ function formatSourceDate(raw, opts = {}) {
827
+ const d = toDate(raw);
828
+ if (!d) return null;
829
+ const dayFrame = u(d);
830
+ const frame = dayFrame ?? (opts.dialect === "dotnet" ? "local" : "utc");
831
+ const p3 = partsIn(d, frame);
832
+ const format = cleanFormat(opts.format, opts.dialect);
833
+ if (format && opts.dialect) {
834
+ const rendered = renderFormat(format, p3, opts.dialect, opts.culture);
835
+ if (rendered) return rendered;
836
+ }
837
+ return localeDate(p3, !!dayFrame, opts.culture);
838
+ }
839
+ function formatSourceDateFor(raw, columnName2, formats, culture) {
840
+ return formatSourceDate(raw, {
841
+ format: formats?.byColumn?.[columnName2] ?? null,
842
+ dialect: formats?.dialect,
843
+ culture
844
+ });
845
+ }
846
+ 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*$/;
847
+ function toDate(raw) {
848
+ if (raw instanceof Date) return isNaN(raw.getTime()) ? null : raw;
849
+ if (typeof raw !== "string" || !ISO_LIKE_RE.test(raw)) return null;
850
+ const d = p(raw.trim());
851
+ return d && !isNaN(d.getTime()) ? d : null;
852
+ }
853
+ function partsIn(d, frame) {
854
+ return frame === "utc" ? {
855
+ year: d.getUTCFullYear(),
856
+ month: d.getUTCMonth() + 1,
857
+ day: d.getUTCDate(),
858
+ hours: d.getUTCHours(),
859
+ minutes: d.getUTCMinutes(),
860
+ seconds: d.getUTCSeconds(),
861
+ ms: d.getUTCMilliseconds(),
862
+ weekday: d.getUTCDay()
863
+ } : {
864
+ year: d.getFullYear(),
865
+ month: d.getMonth() + 1,
866
+ day: d.getDate(),
867
+ hours: d.getHours(),
868
+ minutes: d.getMinutes(),
869
+ seconds: d.getSeconds(),
870
+ ms: d.getMilliseconds(),
871
+ weekday: d.getDay()
872
+ };
873
+ }
874
+ function utcAnchor(p3) {
875
+ const d = new Date(Date.UTC(p3.year, p3.month - 1, p3.day, p3.hours, p3.minutes, p3.seconds, p3.ms));
876
+ if (p3.year >= 0 && p3.year < 100) d.setUTCFullYear(p3.year);
877
+ return d;
878
+ }
879
+ function localeDate(p3, wholeDay, culture) {
880
+ const anchor = utcAnchor(p3);
881
+ try {
882
+ const o = { dateStyle: "medium", timeZone: "UTC" };
883
+ if (!wholeDay) o.timeStyle = p3.seconds || p3.ms ? "medium" : "short";
884
+ return new Intl.DateTimeFormat(culture || void 0, o).format(anchor);
885
+ } catch {
886
+ const iso = anchor.toISOString();
887
+ return wholeDay ? iso.slice(0, 10) : `${iso.slice(0, 10)} ${iso.slice(11, 19)}`;
888
+ }
889
+ }
890
+ function cleanFormat(raw, dialect) {
891
+ if (!raw || !dialect) return null;
892
+ let s = firstSection(String(raw), dialect);
893
+ if (dialect === "excel") {
894
+ s = s.replace(/\[(h{1,2}|m{1,2}|s{1,2})\]/gi, "$1").replace(/\[[^\]]*\]/g, "");
895
+ }
896
+ if (!s.trim() || /^general$/i.test(s.trim())) return null;
897
+ return hasDateToken(s, dialect) ? s : null;
898
+ }
899
+ function firstSection(s, dialect) {
900
+ let out = "";
901
+ let quote = "";
902
+ for (let i = 0; i < s.length; i++) {
903
+ const ch = s[i];
904
+ if (quote) {
905
+ out += ch;
906
+ if (ch === quote) quote = "";
907
+ continue;
908
+ }
909
+ if (ch === '"' || dialect === "dotnet" && ch === "'") {
910
+ quote = ch;
911
+ out += ch;
912
+ continue;
913
+ }
914
+ if (ch === "\\") {
915
+ out += ch + (s[i + 1] ?? "");
916
+ i++;
917
+ continue;
918
+ }
919
+ if (ch === ";") break;
920
+ out += ch;
921
+ }
922
+ return out;
923
+ }
924
+ function hasDateToken(s, dialect) {
925
+ const stripped = stripLiterals(s, dialect);
926
+ return dialect === "excel" ? /[ymdhs]/i.test(stripped) : /[yMdHhmsft]/.test(stripped);
927
+ }
928
+ function stripLiterals(s, dialect) {
929
+ let out = "";
930
+ for (let i = 0; i < s.length; i++) {
931
+ const ch = s[i];
932
+ if (ch === "\\" || dialect === "dotnet" && ch === "%") {
933
+ i++;
934
+ continue;
935
+ }
936
+ if (ch === '"' || dialect === "dotnet" && ch === "'") {
937
+ const end = s.indexOf(ch, i + 1);
938
+ i = end < 0 ? s.length : end;
939
+ continue;
940
+ }
941
+ out += ch;
942
+ }
943
+ return out;
944
+ }
945
+ var EXCEL_TOKENS = [
946
+ [/^am\/pm/i, "meridiemLong"],
947
+ [/^a\/p/i, "meridiemShort"],
948
+ [/^y{3,}/i, "year4"],
949
+ [/^y{1,2}/i, "year2"],
950
+ [/^m{5,}/i, "monthNarrow"],
951
+ [/^m{4}/i, "monthLong"],
952
+ [/^m{3}/i, "monthShort"],
953
+ [/^m{2}/i, "mAmbiguous2"],
954
+ [/^m/i, "mAmbiguous1"],
955
+ [/^d{4,}/i, "weekdayLong"],
956
+ [/^d{3}/i, "weekdayShort"],
957
+ [/^d{2}/i, "day2"],
958
+ [/^d/i, "day1"],
959
+ [/^h{2,}/i, "hour2"],
960
+ [/^h/i, "hour1"],
961
+ [/^s{2,}/i, "second2"],
962
+ [/^s/i, "second1"]
963
+ ];
964
+ var DOTNET_TOKENS = [
965
+ [/^y{3,}/, "year4"],
966
+ [/^y{1,2}/, "year2"],
967
+ [/^M{4,}/, "monthLong"],
968
+ [/^M{3}/, "monthShort"],
969
+ [/^M{2}/, "month2"],
970
+ [/^M/, "month1"],
971
+ [/^d{4,}/, "weekdayLong"],
972
+ [/^d{3}/, "weekdayShort"],
973
+ [/^d{2}/, "day2"],
974
+ [/^d/, "day1"],
975
+ [/^H{2,}/, "hour2"],
976
+ [/^H/, "hour1"],
977
+ [/^h{2,}/, "hour2"],
978
+ [/^h/, "hour1"],
979
+ [/^m{2,}/, "minute2"],
980
+ [/^m/, "minute1"],
981
+ [/^s{2,}/, "second2"],
982
+ [/^s/, "second1"],
983
+ [/^f+/, "fraction"],
984
+ [/^tt/, "meridiemLong"],
985
+ [/^t/, "meridiemShort"]
986
+ ];
987
+ function tokenize(s, dialect) {
988
+ const table = dialect === "excel" ? EXCEL_TOKENS : DOTNET_TOKENS;
989
+ const out = [];
990
+ let i = 0;
991
+ while (i < s.length) {
992
+ const ch = s[i];
993
+ if (ch === "\\" || dialect === "dotnet" && ch === "%") {
994
+ if (i + 1 < s.length) out.push({ kind: "literal", text: s[i + 1] });
995
+ i += 2;
996
+ continue;
997
+ }
998
+ if (ch === '"' || dialect === "dotnet" && ch === "'") {
999
+ const end = s.indexOf(ch, i + 1);
1000
+ out.push({ kind: "literal", text: end < 0 ? s.slice(i + 1) : s.slice(i + 1, end) });
1001
+ i = end < 0 ? s.length : end + 1;
1002
+ continue;
1003
+ }
1004
+ const rest = s.slice(i);
1005
+ let matched = false;
1006
+ for (const [re, kind] of table) {
1007
+ const m = re.exec(rest);
1008
+ if (m) {
1009
+ out.push({ kind });
1010
+ i += m[0].length;
1011
+ matched = true;
1012
+ break;
1013
+ }
1014
+ }
1015
+ if (!matched) {
1016
+ out.push({ kind: "literal", text: ch });
1017
+ i++;
1018
+ }
1019
+ }
1020
+ return out;
1021
+ }
1022
+ function resolveAmbiguousM(tokens) {
1023
+ const isHour = (t) => t.kind === "hour1" || t.kind === "hour2";
1024
+ const isSecond = (t) => t.kind === "second1" || t.kind === "second2";
1025
+ for (let i = 0; i < tokens.length; i++) {
1026
+ const t = tokens[i];
1027
+ if (t.kind !== "mAmbiguous1" && t.kind !== "mAmbiguous2") continue;
1028
+ const two = t.kind === "mAmbiguous2";
1029
+ let minute = false;
1030
+ for (let j = i - 1; j >= 0; j--) {
1031
+ if (tokens[j].kind === "literal") continue;
1032
+ minute = isHour(tokens[j]);
1033
+ break;
1034
+ }
1035
+ if (!minute) {
1036
+ for (let j = i + 1; j < tokens.length; j++) {
1037
+ if (tokens[j].kind === "literal") continue;
1038
+ minute = isSecond(tokens[j]);
1039
+ break;
1040
+ }
1041
+ }
1042
+ t.kind = minute ? two ? "minute2" : "minute1" : two ? "month2" : "month1";
1043
+ }
1044
+ }
1045
+ var EN_MONTHS_LONG = [
1046
+ "January",
1047
+ "February",
1048
+ "March",
1049
+ "April",
1050
+ "May",
1051
+ "June",
1052
+ "July",
1053
+ "August",
1054
+ "September",
1055
+ "October",
1056
+ "November",
1057
+ "December"
1058
+ ];
1059
+ var EN_MONTHS_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1060
+ var EN_DAYS_LONG = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
1061
+ var EN_DAYS_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
1062
+ function name(p3, kind, width, culture) {
1063
+ try {
1064
+ const opts = kind === "month" ? { month: width, timeZone: "UTC" } : { weekday: width === "narrow" ? "short" : width, timeZone: "UTC" };
1065
+ return new Intl.DateTimeFormat(culture || void 0, opts).format(utcAnchor(p3));
1066
+ } catch {
1067
+ if (kind === "month") {
1068
+ const long = EN_MONTHS_LONG[p3.month - 1] ?? "";
1069
+ if (width === "long") return long;
1070
+ return width === "narrow" ? long.slice(0, 1) : EN_MONTHS_SHORT[p3.month - 1] ?? "";
1071
+ }
1072
+ return (width === "long" ? EN_DAYS_LONG[p3.weekday] : EN_DAYS_SHORT[p3.weekday]) ?? "";
1073
+ }
1074
+ }
1075
+ var p2 = (n) => (n < 10 ? "0" : "") + n;
1076
+ function renderFormat(format, p3, dialect, culture) {
1077
+ const tokens = tokenize(format, dialect);
1078
+ if (dialect === "excel") resolveAmbiguousM(tokens);
1079
+ const twelve = tokens.some((t) => t.kind === "meridiemLong" || t.kind === "meridiemShort");
1080
+ const hour12 = () => {
1081
+ const h = p3.hours % 12;
1082
+ return h === 0 ? 12 : h;
1083
+ };
1084
+ let out = "";
1085
+ let sawField = false;
1086
+ for (const t of tokens) {
1087
+ switch (t.kind) {
1088
+ case "literal":
1089
+ out += t.text ?? "";
1090
+ break;
1091
+ case "year4":
1092
+ out += String(p3.year);
1093
+ sawField = true;
1094
+ break;
1095
+ case "year2":
1096
+ out += p2((p3.year % 100 + 100) % 100);
1097
+ sawField = true;
1098
+ break;
1099
+ case "monthLong":
1100
+ out += name(p3, "month", "long", culture);
1101
+ sawField = true;
1102
+ break;
1103
+ case "monthShort":
1104
+ out += name(p3, "month", "short", culture);
1105
+ sawField = true;
1106
+ break;
1107
+ case "monthNarrow":
1108
+ out += name(p3, "month", "long", culture).slice(0, 1);
1109
+ sawField = true;
1110
+ break;
1111
+ case "month2":
1112
+ out += p2(p3.month);
1113
+ sawField = true;
1114
+ break;
1115
+ case "month1":
1116
+ out += String(p3.month);
1117
+ sawField = true;
1118
+ break;
1119
+ case "weekdayLong":
1120
+ out += name(p3, "weekday", "long", culture);
1121
+ sawField = true;
1122
+ break;
1123
+ case "weekdayShort":
1124
+ out += name(p3, "weekday", "short", culture);
1125
+ sawField = true;
1126
+ break;
1127
+ case "day2":
1128
+ out += p2(p3.day);
1129
+ sawField = true;
1130
+ break;
1131
+ case "day1":
1132
+ out += String(p3.day);
1133
+ sawField = true;
1134
+ break;
1135
+ case "hour2":
1136
+ out += p2(twelve ? hour12() : p3.hours);
1137
+ sawField = true;
1138
+ break;
1139
+ case "hour1":
1140
+ out += String(twelve ? hour12() : p3.hours);
1141
+ sawField = true;
1142
+ break;
1143
+ case "minute2":
1144
+ out += p2(p3.minutes);
1145
+ sawField = true;
1146
+ break;
1147
+ case "minute1":
1148
+ out += String(p3.minutes);
1149
+ sawField = true;
1150
+ break;
1151
+ case "second2":
1152
+ out += p2(p3.seconds);
1153
+ sawField = true;
1154
+ break;
1155
+ case "second1":
1156
+ out += String(p3.seconds);
1157
+ sawField = true;
1158
+ break;
1159
+ case "fraction":
1160
+ out += String(p3.ms).padStart(3, "0");
1161
+ sawField = true;
1162
+ break;
1163
+ case "meridiemLong":
1164
+ out += p3.hours < 12 ? "AM" : "PM";
1165
+ sawField = true;
1166
+ break;
1167
+ case "meridiemShort":
1168
+ out += p3.hours < 12 ? "A" : "P";
1169
+ sawField = true;
1170
+ break;
1171
+ }
1172
+ }
1173
+ return sawField && out.trim() ? out : null;
1174
+ }
1175
+
823
1176
  // src/selectionCard.ts
824
1177
  var SYNTHETIC_PREFIX = "__";
825
1178
  var NUMERIC_DATATYPE = /int|double|decimal|single|float|number|currency|money/i;
@@ -852,8 +1205,8 @@ function aggLabel(agg) {
852
1205
  return "Sum";
853
1206
  }
854
1207
  }
855
- function isSynthetic(name) {
856
- return String(name ?? "").startsWith(SYNTHETIC_PREFIX);
1208
+ function isSynthetic(name2) {
1209
+ return String(name2 ?? "").startsWith(SYNTHETIC_PREFIX);
857
1210
  }
858
1211
  function isMeasureColumn(col) {
859
1212
  if (!col || isSynthetic(col.name)) return false;
@@ -863,16 +1216,31 @@ function isMeasureColumn(col) {
863
1216
  function isRealColumn(col) {
864
1217
  return !!col && !isSynthetic(col.name);
865
1218
  }
1219
+ function isSerialisedDateColumn(col) {
1220
+ return /^datetime/i.test(String(col?.dataType ?? ""));
1221
+ }
1222
+ function displayText(col, raw, opts) {
1223
+ if (raw == null || raw === "") return "(blank)";
1224
+ if (isSerialisedDateColumn(col)) {
1225
+ const shown = formatSourceDate(raw, {
1226
+ format: opts.sourceFormats?.byColumn?.[String(col?.name ?? "")] ?? null,
1227
+ dialect: opts.sourceFormats?.dialect,
1228
+ culture: opts.cultureCode
1229
+ });
1230
+ if (shown) return shown;
1231
+ }
1232
+ return String(raw);
1233
+ }
866
1234
  function isSuppressed(col) {
867
- return Rt(col).suppressed;
1235
+ return vt(col).suppressed;
868
1236
  }
869
1237
  function resolveAgg(col, requested, auto) {
870
1238
  const fallback = () => {
871
- const d = Ce(col);
1239
+ const d = Me(col);
872
1240
  return d === "first" ? "count" : d;
873
1241
  };
874
1242
  if (auto) return fallback();
875
- return vt(col).indexOf(requested) >= 0 ? requested : fallback();
1243
+ return It(col).indexOf(requested) >= 0 ? requested : fallback();
876
1244
  }
877
1245
  function toNumber(v) {
878
1246
  if (v == null || v === "") return null;
@@ -939,9 +1307,8 @@ function computeSelectionCard(payload, selectedRowIdxs, opts = {}) {
939
1307
  if (dimIdx >= 0) {
940
1308
  const seen = [];
941
1309
  const set = /* @__PURE__ */ new Set();
942
- for (const p of positions) {
943
- const raw = rows[p]?.[dimIdx];
944
- const s = raw == null || raw === "" ? "(blank)" : String(raw);
1310
+ for (const p3 of positions) {
1311
+ const s = displayText(columns[dimIdx], rows[p3]?.[dimIdx], opts);
945
1312
  if (!set.has(s)) {
946
1313
  set.add(s);
947
1314
  seen.push(s);
@@ -969,14 +1336,14 @@ function computeSelectionCard(payload, selectedRowIdxs, opts = {}) {
969
1336
  const colAgg = resolveAgg(col, agg, auto);
970
1337
  const selVals = [];
971
1338
  const selRaw = [];
972
- for (const p of positions) {
973
- const n = toNumber(rows[p]?.[c]);
974
- selRaw.push(rows[p]?.[c]);
1339
+ for (const p3 of positions) {
1340
+ const n = toNumber(rows[p3]?.[c]);
1341
+ selRaw.push(rows[p3]?.[c]);
975
1342
  if (n != null) selVals.push(n);
976
1343
  }
977
1344
  const value = reduce(selVals, colAgg, selRaw);
978
1345
  let sharePct = null;
979
- if (Ae(col, colAgg) && value != null) {
1346
+ if (Te(col, colAgg) && value != null) {
980
1347
  const allVals = [];
981
1348
  const allRaw = [];
982
1349
  for (let r = 0; r < rows.length; r++) {
@@ -1001,20 +1368,20 @@ function computeSelectionCard(payload, selectedRowIdxs, opts = {}) {
1001
1368
  for (let c = 0; c < columns.length && dimensionLines.length < maxDimensions; c++) {
1002
1369
  if (c === dimIdx) continue;
1003
1370
  if (!isRealColumn(columns[c]) || isMeasureColumn(columns[c])) continue;
1004
- const name = String(columns[c]?.name ?? "");
1371
+ const name2 = String(columns[c]?.name ?? "");
1005
1372
  const distinct = /* @__PURE__ */ new Set();
1006
1373
  let firstText = "";
1007
- for (const p of positions) {
1008
- const raw = rows[p]?.[c];
1009
- const s = raw == null || raw === "" ? "(blank)" : String(raw);
1010
- if (!distinct.size) firstText = s;
1011
- distinct.add(s);
1374
+ for (const p3 of positions) {
1375
+ const raw = rows[p3]?.[c];
1376
+ const key = raw == null || raw === "" ? "(blank)" : String(raw);
1377
+ if (!distinct.size) firstText = displayText(columns[c], raw, opts);
1378
+ distinct.add(key);
1012
1379
  }
1013
1380
  if (!distinct.size) continue;
1014
1381
  const single = distinct.size === 1;
1015
1382
  dimensionLines.push({
1016
- column: name,
1017
- label: single ? name : `Distinct ${name}`,
1383
+ column: name2,
1384
+ label: single ? name2 : `Distinct ${name2}`,
1018
1385
  value: single ? null : distinct.size,
1019
1386
  valueText: single ? firstText : fmtNumber(distinct.size, opts.cultureCode),
1020
1387
  sharePct: null,
@@ -1137,6 +1504,8 @@ export {
1137
1504
  filterQualifyRows,
1138
1505
  fitReadingFor,
1139
1506
  fitRenderedChart,
1507
+ formatSourceDate,
1508
+ formatSourceDateFor,
1140
1509
  geoAssetFor,
1141
1510
  geoFromCache,
1142
1511
  glyphSampleGrid,
package/dist/react.mjs CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  createChartHost,
5
5
  geoFromCache,
6
6
  loadGeo
7
- } from "./chunk-R4STRHXO.mjs";
7
+ } from "./chunk-NWMMNXH2.mjs";
8
8
  import "./chunk-A2GMXZP7.mjs";
9
9
  import "./chunk-GHODWOAL.mjs";
10
10
 
@@ -15,6 +15,7 @@ export { qualifyGroupHeadingFor, newQualifyGroupState, type QualifyGroupRow, typ
15
15
  export { qualifyPick, qualifyAuto, qualifyCancel, launchGenerates, launchFavorStyle, chooserFitsViewport, shouldOpenChooserOnGenerate, shouldOpenInlineChooserOnGenerate, canConfirmLaunch, confirmLaunch, qualifyFailureFallsOpen, CHOOSER_MIN_WIDTH_PX, CHOOSER_MIN_HEIGHT_PX, type QualifyLaunchOutcome, type ChooserGateInput, } from "./qualifyLaunch";
16
16
  export { normalizeFilterTerm, filterQualifyRows, readQualifyChartRow, readQualifyRefusalRow, filterFitsChooser, listNeedsFilter, qualifyFilterGate, inlineFilterGate, FILTER_MIN_TERM_CHARS, FILTER_ROW_PX, FILTER_MIN_WIDTH_PX, FILTER_MIN_HEIGHT_PX, INLINE_FILTER_MIN_ROWS, computeQualifyFilterView, qualifyFilterCountText, type QualifyFilterTier, type QualifyFilterResult, type QualifyFilterRead, type QualifyFilterGateReason, type QualifyFilterGateInput, type QualifyFilterRow, type QualifyFilterGroup, type QualifyFilterView, type QualifyFilterNoteKind, } from "./qualifyFilter";
17
17
  export { computeSelectionCard, normaliseAggregation, type SelectionCardModel, type SelectionCardLine, type SelectionCardOptions, } from "./selectionCard";
18
+ export { formatSourceDate, formatSourceDateFor, type SourceFormats, type SourceFormatDialect, type SourceDateOptions, } from "./sourceDateFormat";
18
19
  export { ensureCrossfilterHitTargets, type HitTargetReport } from "./hitTargets";
19
20
  export { applyLabelContrast, LABEL_CONTRAST_DONE_ATTR, LABEL_CONTRAST_CAP, type LabelContrastOptions, type LabelContrastReport, } from "./labelContrastDom";
20
21
  export { decideLabelColor, toRGBA, compositeOver, relativeLuminance, contrastRatio, isPillBackdropAlpha, pillBacksGlyph, backingHoldsGlyph, cellSuppressesNormalize, glyphSampleGrid, DARK_TEXT, LIGHT_TEXT, MIN_CONTRAST, WHITE_TEXT_BG_LUM, PILL_MIN_ALPHA, PILL_OPAQUE_ALPHA, PILL_MIN_COVERAGE, PAGE_MATCH_TOLERANCE, BACKING_MAJORITY, GLYPH_SAMPLE_N, type LabelDecision, } from "./labelContrast";
@@ -1,4 +1,5 @@
1
1
  import type { RenderPayload } from "./payload";
2
+ import { type SourceFormats } from "./sourceDateFormat";
2
3
  export interface SelectionCardOptions {
3
4
  /** The chart's own aggregation, so the card AGREES with the picture it sits on. A card
4
5
  * reading "Sum" beside a chart drawn from averages is two answers to one question.
@@ -24,6 +25,16 @@ export interface SelectionCardOptions {
24
25
  * cap already had (on the sample table the two coordinate columns took two of the four
25
26
  * measure slots and pushed the real measures under "+2 more"). Default 2. */
26
27
  maxDimensions?: number;
28
+ /** THE SOURCE'S OWN DATE FORMATS, keyed by column name, plus the dialect they are written in
29
+ * — Excel's `range.numberFormat` for the add-in, a `.NET` format string for the visual.
30
+ *
31
+ * Optional, and the fallback is deliberately good rather than minimal: a host that supplies
32
+ * nothing still gets a locale date instead of the ISO instant this replaced, so the MCP and
33
+ * React hosts (a CSV has no cell formatting to read) and every chart cached before today
34
+ * improve without anyone plumbing anything. What the map buys is AGREEMENT with the cells
35
+ * the card is floating over — a sheet that shows `31-Aug-25` gets a card that says
36
+ * `31-Aug-25`. */
37
+ sourceFormats?: SourceFormats | null;
27
38
  }
28
39
  export interface SelectionCardLine {
29
40
  column: string;