exploreda 0.0.6 → 0.1.0

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.
Files changed (208) hide show
  1. package/README.md +144 -0
  2. package/dist/ExplorEda.cjs +4608 -11288
  3. package/dist/ExplorEda.cjs.map +1 -1
  4. package/dist/ExplorEda.css +2282 -1689
  5. package/dist/ExplorEda.css.map +1 -1
  6. package/dist/ExplorEda.d.cts +124 -278
  7. package/dist/ExplorEda.d.ts +124 -278
  8. package/dist/ExplorEda.js +4479 -11166
  9. package/dist/ExplorEda.js.map +1 -1
  10. package/dist/calculations.cjs +18 -0
  11. package/dist/calculations.cjs.map +1 -0
  12. package/dist/calculations.d.cts +63 -0
  13. package/dist/calculations.d.ts +63 -0
  14. package/dist/calculations.js +3 -0
  15. package/dist/calculations.js.map +1 -0
  16. package/dist/charts/bar.cjs +27 -0
  17. package/dist/charts/bar.cjs.map +1 -0
  18. package/dist/charts/bar.d.cts +2 -0
  19. package/dist/charts/bar.d.ts +2 -0
  20. package/dist/charts/bar.js +18 -0
  21. package/dist/charts/bar.js.map +1 -0
  22. package/dist/charts/box-plot.cjs +26 -0
  23. package/dist/charts/box-plot.cjs.map +1 -0
  24. package/dist/charts/box-plot.d.cts +2 -0
  25. package/dist/charts/box-plot.d.ts +2 -0
  26. package/dist/charts/box-plot.js +17 -0
  27. package/dist/charts/box-plot.js.map +1 -0
  28. package/dist/charts/color-legend.cjs +22 -0
  29. package/dist/charts/color-legend.cjs.map +1 -0
  30. package/dist/charts/color-legend.d.cts +2 -0
  31. package/dist/charts/color-legend.d.ts +2 -0
  32. package/dist/charts/color-legend.js +13 -0
  33. package/dist/charts/color-legend.js.map +1 -0
  34. package/dist/charts/data-table.cjs +22 -0
  35. package/dist/charts/data-table.cjs.map +1 -0
  36. package/dist/charts/data-table.d.cts +2 -0
  37. package/dist/charts/data-table.d.ts +2 -0
  38. package/dist/charts/data-table.js +13 -0
  39. package/dist/charts/data-table.js.map +1 -0
  40. package/dist/charts/line.cjs +30 -0
  41. package/dist/charts/line.cjs.map +1 -0
  42. package/dist/charts/line.d.cts +2 -0
  43. package/dist/charts/line.d.ts +2 -0
  44. package/dist/charts/line.js +17 -0
  45. package/dist/charts/line.js.map +1 -0
  46. package/dist/charts/markdown.cjs +16 -0
  47. package/dist/charts/markdown.cjs.map +1 -0
  48. package/dist/charts/markdown.css +77 -0
  49. package/dist/charts/markdown.css.map +1 -0
  50. package/dist/charts/markdown.d.cts +2 -0
  51. package/dist/charts/markdown.d.ts +2 -0
  52. package/dist/charts/markdown.js +7 -0
  53. package/dist/charts/markdown.js.map +1 -0
  54. package/dist/charts/pivot-table.cjs +23 -0
  55. package/dist/charts/pivot-table.cjs.map +1 -0
  56. package/dist/charts/pivot-table.d.cts +2 -0
  57. package/dist/charts/pivot-table.d.ts +2 -0
  58. package/dist/charts/pivot-table.js +14 -0
  59. package/dist/charts/pivot-table.js.map +1 -0
  60. package/dist/charts/row.cjs +27 -0
  61. package/dist/charts/row.cjs.map +1 -0
  62. package/dist/charts/row.d.cts +6 -0
  63. package/dist/charts/row.d.ts +6 -0
  64. package/dist/charts/row.js +18 -0
  65. package/dist/charts/row.js.map +1 -0
  66. package/dist/charts/scatter.cjs +24 -0
  67. package/dist/charts/scatter.cjs.map +1 -0
  68. package/dist/charts/scatter.d.cts +2 -0
  69. package/dist/charts/scatter.d.ts +2 -0
  70. package/dist/charts/scatter.js +15 -0
  71. package/dist/charts/scatter.js.map +1 -0
  72. package/dist/charts/summary-table.cjs +19 -0
  73. package/dist/charts/summary-table.cjs.map +1 -0
  74. package/dist/charts/summary-table.d.cts +2 -0
  75. package/dist/charts/summary-table.d.ts +2 -0
  76. package/dist/charts/summary-table.js +10 -0
  77. package/dist/charts/summary-table.js.map +1 -0
  78. package/dist/charts/three-d-scatter.cjs +24 -0
  79. package/dist/charts/three-d-scatter.cjs.map +1 -0
  80. package/dist/charts/three-d-scatter.d.cts +6 -0
  81. package/dist/charts/three-d-scatter.d.ts +6 -0
  82. package/dist/charts/three-d-scatter.js +15 -0
  83. package/dist/charts/three-d-scatter.js.map +1 -0
  84. package/dist/chunk-3PGYFLE3.cjs +499 -0
  85. package/dist/chunk-3PGYFLE3.cjs.map +1 -0
  86. package/dist/chunk-4HNDKMWF.js +167 -0
  87. package/dist/chunk-4HNDKMWF.js.map +1 -0
  88. package/dist/chunk-4QNENKTV.cjs +1214 -0
  89. package/dist/chunk-4QNENKTV.cjs.map +1 -0
  90. package/dist/chunk-54M32JJB.js +132 -0
  91. package/dist/chunk-54M32JJB.js.map +1 -0
  92. package/dist/chunk-5B23JVH3.cjs +2238 -0
  93. package/dist/chunk-5B23JVH3.cjs.map +1 -0
  94. package/dist/chunk-5LH6NSNL.js +456 -0
  95. package/dist/chunk-5LH6NSNL.js.map +1 -0
  96. package/dist/chunk-6DOKKCJ5.cjs +313 -0
  97. package/dist/chunk-6DOKKCJ5.cjs.map +1 -0
  98. package/dist/chunk-6HZX5CBT.cjs +1074 -0
  99. package/dist/chunk-6HZX5CBT.cjs.map +1 -0
  100. package/dist/chunk-6U5DOIQM.js +1071 -0
  101. package/dist/chunk-6U5DOIQM.js.map +1 -0
  102. package/dist/chunk-7GBZIK2R.cjs +80 -0
  103. package/dist/chunk-7GBZIK2R.cjs.map +1 -0
  104. package/dist/chunk-7PTJNIOD.js +520 -0
  105. package/dist/chunk-7PTJNIOD.js.map +1 -0
  106. package/dist/chunk-AHZYQ7FN.js +319 -0
  107. package/dist/chunk-AHZYQ7FN.js.map +1 -0
  108. package/dist/chunk-AOODAOL4.cjs +55 -0
  109. package/dist/chunk-AOODAOL4.cjs.map +1 -0
  110. package/dist/chunk-ASKFIRMC.cjs +165 -0
  111. package/dist/chunk-ASKFIRMC.cjs.map +1 -0
  112. package/dist/chunk-B7OQUBH4.cjs +171 -0
  113. package/dist/chunk-B7OQUBH4.cjs.map +1 -0
  114. package/dist/chunk-BIMEHDLB.js +72 -0
  115. package/dist/chunk-BIMEHDLB.js.map +1 -0
  116. package/dist/chunk-C62KHV7S.js +1225 -0
  117. package/dist/chunk-C62KHV7S.js.map +1 -0
  118. package/dist/chunk-C7GQT65O.cjs +1119 -0
  119. package/dist/chunk-C7GQT65O.cjs.map +1 -0
  120. package/dist/chunk-CGDWDV4Z.js +1211 -0
  121. package/dist/chunk-CGDWDV4Z.js.map +1 -0
  122. package/dist/chunk-CJQQFNEY.js +69 -0
  123. package/dist/chunk-CJQQFNEY.js.map +1 -0
  124. package/dist/chunk-DHYLSXNR.js +964 -0
  125. package/dist/chunk-DHYLSXNR.js.map +1 -0
  126. package/dist/chunk-DKFA4KOJ.cjs +87 -0
  127. package/dist/chunk-DKFA4KOJ.cjs.map +1 -0
  128. package/dist/chunk-DS5BS7PP.cjs +357 -0
  129. package/dist/chunk-DS5BS7PP.cjs.map +1 -0
  130. package/dist/chunk-EGMFQSGK.js +909 -0
  131. package/dist/chunk-EGMFQSGK.js.map +1 -0
  132. package/dist/chunk-EU5EFHO5.cjs +53 -0
  133. package/dist/chunk-EU5EFHO5.cjs.map +1 -0
  134. package/dist/chunk-FM46QSDM.js +837 -0
  135. package/dist/chunk-FM46QSDM.js.map +1 -0
  136. package/dist/chunk-GV2LRR32.cjs +843 -0
  137. package/dist/chunk-GV2LRR32.cjs.map +1 -0
  138. package/dist/chunk-H3XKFSA5.cjs +322 -0
  139. package/dist/chunk-H3XKFSA5.cjs.map +1 -0
  140. package/dist/chunk-H5AVMMCE.js +347 -0
  141. package/dist/chunk-H5AVMMCE.js.map +1 -0
  142. package/dist/chunk-JDMUN4VK.cjs +986 -0
  143. package/dist/chunk-JDMUN4VK.cjs.map +1 -0
  144. package/dist/chunk-JNO7OXCC.js +2171 -0
  145. package/dist/chunk-JNO7OXCC.js.map +1 -0
  146. package/dist/chunk-JQMG4ZR7.js +111 -0
  147. package/dist/chunk-JQMG4ZR7.js.map +1 -0
  148. package/dist/chunk-L2Q2R43U.cjs +118 -0
  149. package/dist/chunk-L2Q2R43U.cjs.map +1 -0
  150. package/dist/chunk-LBTZVERO.js +466 -0
  151. package/dist/chunk-LBTZVERO.js.map +1 -0
  152. package/dist/chunk-LUUGGTTK.cjs +1227 -0
  153. package/dist/chunk-LUUGGTTK.cjs.map +1 -0
  154. package/dist/chunk-MEEPH7ZC.cjs +911 -0
  155. package/dist/chunk-MEEPH7ZC.cjs.map +1 -0
  156. package/dist/chunk-MJPDJ5AS.js +184 -0
  157. package/dist/chunk-MJPDJ5AS.js.map +1 -0
  158. package/dist/chunk-MJROUIDZ.cjs +219 -0
  159. package/dist/chunk-MJROUIDZ.cjs.map +1 -0
  160. package/dist/chunk-MUWSBYJS.js +528 -0
  161. package/dist/chunk-MUWSBYJS.js.map +1 -0
  162. package/dist/chunk-NDF5LCST.js +1088 -0
  163. package/dist/chunk-NDF5LCST.js.map +1 -0
  164. package/dist/chunk-NGWTPED4.js +311 -0
  165. package/dist/chunk-NGWTPED4.js.map +1 -0
  166. package/dist/chunk-PP4VCONW.js +739 -0
  167. package/dist/chunk-PP4VCONW.js.map +1 -0
  168. package/dist/chunk-PRMC2A6P.cjs +488 -0
  169. package/dist/chunk-PRMC2A6P.cjs.map +1 -0
  170. package/dist/chunk-PST6UU6S.cjs +742 -0
  171. package/dist/chunk-PST6UU6S.cjs.map +1 -0
  172. package/dist/chunk-Q7LK2UWH.js +33 -0
  173. package/dist/chunk-Q7LK2UWH.js.map +1 -0
  174. package/dist/chunk-SFV5LZPD.js +162 -0
  175. package/dist/chunk-SFV5LZPD.js.map +1 -0
  176. package/dist/chunk-SKXGYJJG.cjs +370 -0
  177. package/dist/chunk-SKXGYJJG.cjs.map +1 -0
  178. package/dist/chunk-SWLNKY6R.js +477 -0
  179. package/dist/chunk-SWLNKY6R.js.map +1 -0
  180. package/dist/chunk-TGVLNHUB.cjs +483 -0
  181. package/dist/chunk-TGVLNHUB.cjs.map +1 -0
  182. package/dist/chunk-TLLRW6S3.js +74 -0
  183. package/dist/chunk-TLLRW6S3.js.map +1 -0
  184. package/dist/chunk-TRAIXZ6M.cjs +140 -0
  185. package/dist/chunk-TRAIXZ6M.cjs.map +1 -0
  186. package/dist/chunk-U74A5VK5.cjs +74 -0
  187. package/dist/chunk-U74A5VK5.cjs.map +1 -0
  188. package/dist/chunk-UAVDLBTA.cjs +74 -0
  189. package/dist/chunk-UAVDLBTA.cjs.map +1 -0
  190. package/dist/chunk-UJWS6TRV.js +354 -0
  191. package/dist/chunk-UJWS6TRV.js.map +1 -0
  192. package/dist/chunk-UUJMUPB3.cjs +531 -0
  193. package/dist/chunk-UUJMUPB3.cjs.map +1 -0
  194. package/dist/chunk-VTKHZIJB.cjs +549 -0
  195. package/dist/chunk-VTKHZIJB.cjs.map +1 -0
  196. package/dist/chunk-YXPAWUXO.js +51 -0
  197. package/dist/chunk-YXPAWUXO.js.map +1 -0
  198. package/dist/chunk-ZD64SCZB.js +80 -0
  199. package/dist/chunk-ZD64SCZB.js.map +1 -0
  200. package/dist/core.cjs +28 -0
  201. package/dist/core.cjs.map +1 -0
  202. package/dist/core.d.cts +22 -0
  203. package/dist/core.d.ts +22 -0
  204. package/dist/core.js +3 -0
  205. package/dist/core.js.map +1 -0
  206. package/dist/dataLayerState-B4XmQjQN.d.cts +258 -0
  207. package/dist/dataLayerState-B4XmQjQN.d.ts +258 -0
  208. package/package.json +30 -52
@@ -0,0 +1,1119 @@
1
+ 'use strict';
2
+
3
+ var chunkMJROUIDZ_cjs = require('./chunk-MJROUIDZ.cjs');
4
+ var chunk5B23JVH3_cjs = require('./chunk-5B23JVH3.cjs');
5
+ var chunkSKXGYJJG_cjs = require('./chunk-SKXGYJJG.cjs');
6
+ var react = require('react');
7
+ var TabsPrimitive = require('@radix-ui/react-tabs');
8
+ var jsxRuntime = require('react/jsx-runtime');
9
+ var lucideReact = require('lucide-react');
10
+
11
+ function _interopNamespace(e) {
12
+ if (e && e.__esModule) return e;
13
+ var n = Object.create(null);
14
+ if (e) {
15
+ Object.keys(e).forEach(function (k) {
16
+ if (k !== 'default') {
17
+ var d = Object.getOwnPropertyDescriptor(e, k);
18
+ Object.defineProperty(n, k, d.get ? d : {
19
+ enumerable: true,
20
+ get: function () { return e[k]; }
21
+ });
22
+ }
23
+ });
24
+ }
25
+ n.default = e;
26
+ return Object.freeze(n);
27
+ }
28
+
29
+ var TabsPrimitive__namespace = /*#__PURE__*/_interopNamespace(TabsPrimitive);
30
+
31
+ function Tabs({
32
+ className,
33
+ ...props
34
+ }) {
35
+ return /* @__PURE__ */ jsxRuntime.jsx(
36
+ TabsPrimitive__namespace.Root,
37
+ {
38
+ "data-slot": "tabs",
39
+ className: chunk5B23JVH3_cjs.cn("flex flex-col gap-2", className),
40
+ ...props
41
+ }
42
+ );
43
+ }
44
+ function TabsList({
45
+ className,
46
+ ...props
47
+ }) {
48
+ return /* @__PURE__ */ jsxRuntime.jsx(
49
+ TabsPrimitive__namespace.List,
50
+ {
51
+ "data-slot": "tabs-list",
52
+ className: chunk5B23JVH3_cjs.cn(
53
+ "bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-1",
54
+ className
55
+ ),
56
+ ...props
57
+ }
58
+ );
59
+ }
60
+ function TabsTrigger({
61
+ className,
62
+ ...props
63
+ }) {
64
+ return /* @__PURE__ */ jsxRuntime.jsx(
65
+ TabsPrimitive__namespace.Trigger,
66
+ {
67
+ "data-slot": "tabs-trigger",
68
+ className: chunk5B23JVH3_cjs.cn(
69
+ "data-[state=active]:bg-background data-[state=active]:text-foreground ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 inline-flex items-center justify-center gap-2 rounded-md px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-4 focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 aria-invalid:focus-visible:ring-0 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
70
+ className
71
+ ),
72
+ ...props
73
+ }
74
+ );
75
+ }
76
+ function TabsContent({
77
+ className,
78
+ ...props
79
+ }) {
80
+ return /* @__PURE__ */ jsxRuntime.jsx(
81
+ TabsPrimitive__namespace.Content,
82
+ {
83
+ "data-slot": "tabs-content",
84
+ className: chunk5B23JVH3_cjs.cn(
85
+ "ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 flex-1 transition-[color,box-shadow] focus-visible:ring-4 focus-visible:outline-1 aria-invalid:focus-visible:ring-0",
86
+ className
87
+ ),
88
+ ...props
89
+ }
90
+ );
91
+ }
92
+ var typeLabels = {
93
+ numeric: "Number",
94
+ categorical: "Text",
95
+ datetime: "Date",
96
+ boolean: "Boolean"
97
+ };
98
+ var typeIcons = {
99
+ numeric: lucideReact.Hash,
100
+ categorical: lucideReact.Type,
101
+ datetime: lucideReact.CalendarDays,
102
+ boolean: lucideReact.ToggleLeft
103
+ };
104
+ var valueLabel = (value) => {
105
+ if (value == null || value === "") return "\u2014";
106
+ if (typeof value === "number") {
107
+ return new Intl.NumberFormat("en-US", { maximumFractionDigits: 3 }).format(
108
+ value
109
+ );
110
+ }
111
+ return String(value);
112
+ };
113
+ function resolveFieldProfile(name, profiles, getColumnData) {
114
+ const profile = profiles.find((item) => item.name === name);
115
+ if (profile) return profile;
116
+ const columnData = getColumnData(name);
117
+ return Object.keys(columnData).length ? chunk5B23JVH3_cjs.buildFieldProfile(name, columnData) : void 0;
118
+ }
119
+ function dateRange(profile) {
120
+ if (profile.dataType !== "datetime") return void 0;
121
+ let first;
122
+ let last;
123
+ let firstTime = Infinity;
124
+ let lastTime = -Infinity;
125
+ for (const { value } of profile.categories?.distribution ?? []) {
126
+ const time = Date.parse(String(value));
127
+ if (!Number.isFinite(time)) continue;
128
+ if (time < firstTime) {
129
+ first = value;
130
+ firstTime = time;
131
+ }
132
+ if (time > lastTime) {
133
+ last = value;
134
+ lastTime = time;
135
+ }
136
+ }
137
+ if (first == null || last == null) return void 0;
138
+ const start = String(first);
139
+ const end = String(last);
140
+ return start === end ? start : `${start}\u2013${end}`;
141
+ }
142
+ function fieldMetadata(profile) {
143
+ const range = profile.statistics ? `${valueLabel(profile.statistics.min)}\u2013${valueLabel(profile.statistics.max)}` : dateRange(profile);
144
+ const sample = profile.categories?.topValues[0] ? valueLabel(profile.categories.topValues[0].value) : void 0;
145
+ return {
146
+ type: typeLabels[profile.dataType],
147
+ detail: range ?? (sample ? `e.g. ${sample}` : "No sample"),
148
+ detailLabel: range ? "Range" : "Sample",
149
+ nulls: `${profile.nullCount.toLocaleString()} null${profile.nullCount === 1 ? "" : "s"}`,
150
+ excluded: profile.excludedCount ? `${profile.excludedCount.toLocaleString()} not finite` : void 0
151
+ };
152
+ }
153
+ function FieldMetadata({
154
+ profile,
155
+ label,
156
+ compact = false,
157
+ showDetail = true,
158
+ className,
159
+ tooltipSide = "bottom"
160
+ }) {
161
+ if (!profile) return /* @__PURE__ */ jsxRuntime.jsx("span", { className, children: label });
162
+ const metadata = fieldMetadata(profile);
163
+ const description = [
164
+ metadata.type,
165
+ metadata.detail,
166
+ metadata.nulls,
167
+ metadata.excluded
168
+ ].filter(Boolean).join("; ");
169
+ const TypeIcon = typeIcons[profile.dataType];
170
+ return /* @__PURE__ */ jsxRuntime.jsx(chunk5B23JVH3_cjs.TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs(chunk5B23JVH3_cjs.Tooltip, { children: [
171
+ /* @__PURE__ */ jsxRuntime.jsx(chunk5B23JVH3_cjs.TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
172
+ "span",
173
+ {
174
+ className: chunk5B23JVH3_cjs.cn(
175
+ compact ? "inline-flex min-w-0 items-center gap-1" : "inline-flex min-w-0 items-baseline gap-1.5",
176
+ className
177
+ ),
178
+ "aria-label": `${label ?? profile.name}: ${description}`,
179
+ children: [
180
+ compact && /* @__PURE__ */ jsxRuntime.jsx(
181
+ TypeIcon,
182
+ {
183
+ className: "h-3.5 w-3.5 shrink-0 text-muted-foreground",
184
+ "aria-hidden": "true"
185
+ }
186
+ ),
187
+ label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate font-medium", children: label }),
188
+ !compact && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 gap-1.5 text-[10px] text-muted-foreground", children: [
189
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0", children: metadata.type }),
190
+ showDetail && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: metadata.detail }),
191
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0", children: metadata.nulls })
192
+ ] })
193
+ ]
194
+ }
195
+ ) }),
196
+ /* @__PURE__ */ jsxRuntime.jsxs(chunk5B23JVH3_cjs.TooltipContent, { side: tooltipSide, align: "start", collisionPadding: 12, children: [
197
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 font-semibold", children: label ?? profile.name }),
198
+ /* @__PURE__ */ jsxRuntime.jsxs("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 text-xs", children: [
199
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: "Type" }),
200
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: metadata.type }),
201
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: metadata.detailLabel }),
202
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: metadata.detail }),
203
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: "Nulls" }),
204
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: metadata.nulls }),
205
+ metadata.excluded && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
206
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: "Excluded" }),
207
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: metadata.excluded })
208
+ ] }),
209
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: "Distinct" }),
210
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: profile.uniqueCount.toLocaleString() })
211
+ ] })
212
+ ] })
213
+ ] }) });
214
+ }
215
+ function CalculationForm({
216
+ draft,
217
+ onChange,
218
+ fields,
219
+ calculatedFields,
220
+ editing,
221
+ nameLocked,
222
+ checking,
223
+ canApply,
224
+ dirty,
225
+ status,
226
+ preview,
227
+ trace,
228
+ impact,
229
+ onApply,
230
+ onDiscard,
231
+ onClose
232
+ }) {
233
+ const fieldProfiles = chunk5B23JVH3_cjs.useDataLayer((state) => state.fieldProfiles);
234
+ const getColumnData = chunk5B23JVH3_cjs.useDataLayer((state) => state.getColumnData);
235
+ const input = react.useRef(null);
236
+ const [search, setSearch] = react.useState("");
237
+ const [tab, setTab] = react.useState(editing ? "chain" : "fields");
238
+ const insert = (text, selectStart = text.length, selectEnd = selectStart) => {
239
+ const start = input.current?.selectionStart ?? draft.expression.length;
240
+ const end = input.current?.selectionEnd ?? start;
241
+ onChange({
242
+ ...draft,
243
+ expression: draft.expression.slice(0, start) + text + draft.expression.slice(end)
244
+ });
245
+ requestAnimationFrame(() => {
246
+ input.current?.focus();
247
+ input.current?.setSelectionRange(start + selectStart, start + selectEnd);
248
+ });
249
+ };
250
+ const matches = fields.filter(
251
+ (field) => field.toLowerCase().includes(search.toLowerCase())
252
+ );
253
+ return /* @__PURE__ */ jsxRuntime.jsxs(
254
+ "form",
255
+ {
256
+ className: "eda-calc-form",
257
+ onSubmit: (event) => {
258
+ event.preventDefault();
259
+ if (canApply) onApply();
260
+ },
261
+ onKeyDown: (event) => {
262
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
263
+ event.preventDefault();
264
+ if (canApply) onApply();
265
+ }
266
+ },
267
+ children: [
268
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "eda-calc-editor-grid", children: [
269
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "eda-calc-main", children: [
270
+ /* @__PURE__ */ jsxRuntime.jsxs(
271
+ "div",
272
+ {
273
+ className: nameLocked ? "eda-calc-name eda-calc-name-locked" : "eda-calc-name",
274
+ children: [
275
+ /* @__PURE__ */ jsxRuntime.jsx(chunkMJROUIDZ_cjs.Label, { htmlFor: "calculation-name", children: "Field name" }),
276
+ /* @__PURE__ */ jsxRuntime.jsx(
277
+ chunkMJROUIDZ_cjs.Input,
278
+ {
279
+ id: "calculation-name",
280
+ value: draft.name,
281
+ readOnly: nameLocked,
282
+ onChange: (event) => onChange({ ...draft, name: event.target.value }),
283
+ placeholder: "e.g. Net sales"
284
+ }
285
+ ),
286
+ nameLocked && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "eda-calc-help", children: "This name is used by other calculations or views. Its formula can still change." })
287
+ ]
288
+ }
289
+ ),
290
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "eda-calc-section-heading", children: [
291
+ /* @__PURE__ */ jsxRuntime.jsx(chunkMJROUIDZ_cjs.Label, { htmlFor: "calculation-expression", children: "Formula" }),
292
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "One result per source row \xB7 UTC dates" })
293
+ ] }),
294
+ /* @__PURE__ */ jsxRuntime.jsx(
295
+ "textarea",
296
+ {
297
+ ref: input,
298
+ id: "calculation-expression",
299
+ value: draft.expression,
300
+ spellCheck: false,
301
+ autoCapitalize: "off",
302
+ autoComplete: "off",
303
+ placeholder: 'e.g. ["Revenue"] - ["Cost"]',
304
+ onChange: (event) => onChange({ ...draft, expression: event.target.value }),
305
+ "aria-describedby": "calculation-syntax calculation-validation"
306
+ }
307
+ ),
308
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "eda-calc-formula-tools", children: [
309
+ /* @__PURE__ */ jsxRuntime.jsx("p", { id: "calculation-syntax", className: "eda-calc-help", children: 'Use ["Field name"] for a field and "text" for a literal.' }),
310
+ /* @__PURE__ */ jsxRuntime.jsx(
311
+ "button",
312
+ {
313
+ type: "button",
314
+ className: "eda-calc-text-button",
315
+ onClick: () => setTab("functions"),
316
+ children: "Function help"
317
+ }
318
+ )
319
+ ] }),
320
+ /* @__PURE__ */ jsxRuntime.jsx(
321
+ "div",
322
+ {
323
+ id: "calculation-validation",
324
+ className: "eda-calc-validation",
325
+ "aria-live": "polite",
326
+ "aria-busy": checking,
327
+ children: status
328
+ }
329
+ ),
330
+ preview
331
+ ] }),
332
+ /* @__PURE__ */ jsxRuntime.jsx("aside", { className: "eda-calc-aside", "aria-label": "Calculation tools", children: /* @__PURE__ */ jsxRuntime.jsxs(Tabs, { value: tab, onValueChange: setTab, children: [
333
+ /* @__PURE__ */ jsxRuntime.jsxs(TabsList, { className: "w-full", children: [
334
+ /* @__PURE__ */ jsxRuntime.jsx(TabsTrigger, { value: "chain", children: "Chain" }),
335
+ /* @__PURE__ */ jsxRuntime.jsx(TabsTrigger, { value: "fields", children: "Fields" }),
336
+ /* @__PURE__ */ jsxRuntime.jsx(TabsTrigger, { value: "functions", children: "Functions" })
337
+ ] }),
338
+ /* @__PURE__ */ jsxRuntime.jsx(TabsContent, { value: "chain", children: trace }),
339
+ /* @__PURE__ */ jsxRuntime.jsxs(TabsContent, { value: "fields", children: [
340
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "eda-calc-help mb-3", children: "Insert at the cursor, or replace selected text." }),
341
+ /* @__PURE__ */ jsxRuntime.jsx(
342
+ chunkMJROUIDZ_cjs.Input,
343
+ {
344
+ "aria-label": "Find a field",
345
+ placeholder: "Find a field\u2026",
346
+ value: search,
347
+ onChange: (event) => setSearch(event.target.value)
348
+ }
349
+ ),
350
+ [true, false].map((calculated) => {
351
+ const group = matches.filter(
352
+ (field) => calculatedFields.includes(field) === calculated
353
+ );
354
+ return group.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
355
+ "section",
356
+ {
357
+ className: "eda-calc-field-group",
358
+ children: [
359
+ /* @__PURE__ */ jsxRuntime.jsxs("h3", { children: [
360
+ calculated ? "Calculated fields" : "Source fields",
361
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: group.length })
362
+ ] }),
363
+ group.map((field) => /* @__PURE__ */ jsxRuntime.jsxs(
364
+ "button",
365
+ {
366
+ type: "button",
367
+ onClick: () => insert("[" + JSON.stringify(field) + "]"),
368
+ children: [
369
+ /* @__PURE__ */ jsxRuntime.jsx(
370
+ "span",
371
+ {
372
+ className: calculated ? "eda-calc-symbol" : "text-muted-foreground",
373
+ children: calculated ? "\u0192x" : "\xB7"
374
+ }
375
+ ),
376
+ /* @__PURE__ */ jsxRuntime.jsx(
377
+ FieldMetadata,
378
+ {
379
+ profile: resolveFieldProfile(
380
+ field,
381
+ fieldProfiles,
382
+ getColumnData
383
+ ),
384
+ label: field,
385
+ compact: true,
386
+ className: "min-w-0 flex-1"
387
+ }
388
+ ),
389
+ /* @__PURE__ */ jsxRuntime.jsx(
390
+ "span",
391
+ {
392
+ className: "ml-auto text-muted-foreground",
393
+ "aria-hidden": "true",
394
+ children: "+"
395
+ }
396
+ )
397
+ ]
398
+ },
399
+ field
400
+ ))
401
+ ]
402
+ },
403
+ String(calculated)
404
+ );
405
+ }),
406
+ !matches.length && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "eda-calc-help mt-4", children: [
407
+ "No fields match \u201C",
408
+ search,
409
+ "\u201D."
410
+ ] })
411
+ ] }),
412
+ /* @__PURE__ */ jsxRuntime.jsxs(TabsContent, { value: "functions", children: [
413
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "eda-calc-help mb-3", children: "Select a function to insert it. Replace the selected argument with a field or value." }),
414
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "eda-calc-function-list", children: Object.values(chunk5B23JVH3_cjs.calculationFunctions).map((fn) => /* @__PURE__ */ jsxRuntime.jsxs(
415
+ "button",
416
+ {
417
+ type: "button",
418
+ onClick: () => {
419
+ const text = fn.syntax.replace(", \u2026", "");
420
+ const start = text.indexOf("(") + 1;
421
+ const end = text.indexOf(",", start);
422
+ insert(text, start, end === -1 ? text.length - 1 : end);
423
+ },
424
+ children: [
425
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: fn.syntax }),
426
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: fn.description })
427
+ ]
428
+ },
429
+ fn.syntax
430
+ )) }),
431
+ /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "eda-calc-syntax-help", children: [
432
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { children: "Operators and rules" }),
433
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Arithmetic: + \u2212 * / ^" }),
434
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Compare: == != > >= < <=" }),
435
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Logic: && (and), || (or), ! (not)" }),
436
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: "if x > 0 then x else 0" }),
437
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Guard missing values with x == null." }),
438
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Date components: year, month, day, quarter, week (ISO)." })
439
+ ] })
440
+ ] })
441
+ ] }) })
442
+ ] }),
443
+ /* @__PURE__ */ jsxRuntime.jsxs("footer", { className: "eda-calc-editor-footer", children: [
444
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
445
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: impact }),
446
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "eda-calc-help", children: dirty ? "Draft only. Apply to update the analysis. Closing keeps your draft in this session." : "Active filters stay in place when you apply a change." })
447
+ ] }),
448
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
449
+ dirty && /* @__PURE__ */ jsxRuntime.jsx(chunk5B23JVH3_cjs.Button, { type: "button", variant: "ghost", onClick: onDiscard, children: "Discard draft" }),
450
+ /* @__PURE__ */ jsxRuntime.jsx(chunk5B23JVH3_cjs.Button, { type: "button", variant: "outline", onClick: onClose, children: "Close" }),
451
+ /* @__PURE__ */ jsxRuntime.jsxs(chunk5B23JVH3_cjs.Button, { type: "submit", disabled: !canApply, children: [
452
+ editing ? "Apply changes" : "Create calculation",
453
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "eda-calc-shortcut", "aria-hidden": "true", children: "\u2318/Ctrl \u21B5" })
454
+ ] })
455
+ ] })
456
+ ] })
457
+ ]
458
+ }
459
+ );
460
+ }
461
+
462
+ // src/components/calculations/calculationHelpers.ts
463
+ function dependentCalculations(calculations, name) {
464
+ const names = /* @__PURE__ */ new Set([name]);
465
+ let added = true;
466
+ while (added) {
467
+ added = false;
468
+ for (const calc of calculations) {
469
+ if (!names.has(calc.resultColumnName) && calc.expression.dependencies.some((field) => names.has(field))) {
470
+ names.add(calc.resultColumnName);
471
+ added = true;
472
+ }
473
+ }
474
+ }
475
+ return calculations.filter(
476
+ (calc) => calc.resultColumnName !== name && names.has(calc.resultColumnName)
477
+ );
478
+ }
479
+ var numbers = new Intl.NumberFormat("en-US", { maximumFractionDigits: 6 });
480
+ function calculationValue(value) {
481
+ if (value == null) return "Missing";
482
+ if (typeof value === "number") return numbers.format(value);
483
+ if (typeof value === "boolean") return value ? "true" : "false";
484
+ return value === "" ? '""' : value;
485
+ }
486
+ function CalculationPreview({
487
+ calculation,
488
+ manager,
489
+ savedManager,
490
+ data,
491
+ selectedRow,
492
+ onSelectRow,
493
+ changed
494
+ }) {
495
+ const [errorsOnly, setErrorsOnly] = react.useState(false);
496
+ const [page, setPage] = react.useState(
497
+ () => Math.max(
498
+ 0,
499
+ Math.floor(data.findIndex((row) => row.__ID === selectedRow) / 5)
500
+ )
501
+ );
502
+ const name = calculation.resultColumnName;
503
+ const values = manager.executeCalculation(calculation);
504
+ const errors = manager.getErrors(name);
505
+ const dependencies = calculation.expression.dependencies;
506
+ const definitions = manager.getCalculations();
507
+ const saved = savedManager.getCalculations().find((calc) => calc.resultColumnName === name);
508
+ const before = saved ? savedManager.executeCalculation(saved) : null;
509
+ const inputValues = dependencies.map((field) => {
510
+ const calc = definitions.find((item) => item.resultColumnName === field);
511
+ return calc ? manager.executeCalculation(calc) : null;
512
+ });
513
+ const rows = errorsOnly ? data.filter((row) => errors.has(row.__ID)) : data;
514
+ const currentPage = Math.min(
515
+ page,
516
+ Math.max(0, Math.ceil(rows.length / 5) - 1)
517
+ );
518
+ const start = currentPage * 5;
519
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "eda-calc-preview", "aria-label": "Calculation preview", children: [
520
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "eda-calc-section-heading", children: [
521
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Inputs \u2192 result" }),
522
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: changed ? "Draft preview" : "Saved values" })
523
+ ] }),
524
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-3", children: [
525
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "eda-calc-help", children: "All loaded rows, before chart filters. Select a row to follow its values through the chain." }),
526
+ errors.size > 0 && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 text-xs", children: [
527
+ /* @__PURE__ */ jsxRuntime.jsx(
528
+ "input",
529
+ {
530
+ type: "checkbox",
531
+ checked: errorsOnly,
532
+ onChange: (event) => {
533
+ setErrorsOnly(event.target.checked);
534
+ setPage(0);
535
+ }
536
+ }
537
+ ),
538
+ "Failed rows only"
539
+ ] })
540
+ ] }),
541
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "eda-calc-preview-scroll", children: [
542
+ /* @__PURE__ */ jsxRuntime.jsxs("table", { children: [
543
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
544
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Row" }),
545
+ dependencies.map((field) => /* @__PURE__ */ jsxRuntime.jsxs("th", { children: [
546
+ definitions.some(
547
+ (calc) => calc.resultColumnName === field
548
+ ) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "eda-calc-symbol mr-1", children: "\u0192x" }),
549
+ field
550
+ ] }, field)),
551
+ changed && before && /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Saved result" }),
552
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: changed ? "Draft result" : "Result" }),
553
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Status" })
554
+ ] }) }),
555
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.slice(start, start + 5).map((row) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { "data-selected": row.__ID === selectedRow, children: [
556
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: /* @__PURE__ */ jsxRuntime.jsx(
557
+ "button",
558
+ {
559
+ type: "button",
560
+ "aria-label": "Trace row " + (row.__ID + 1),
561
+ "aria-pressed": row.__ID === selectedRow,
562
+ onClick: () => onSelectRow(row.__ID),
563
+ children: row.__ID + 1
564
+ }
565
+ ) }),
566
+ dependencies.map((field, index) => /* @__PURE__ */ jsxRuntime.jsx("td", { children: calculationValue(
567
+ inputValues[index] ? inputValues[index].get(row.__ID) : row[field]
568
+ ) }, field)),
569
+ changed && before && /* @__PURE__ */ jsxRuntime.jsx("td", { className: "text-muted-foreground", children: calculationValue(before.get(row.__ID)) }),
570
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "font-semibold", children: errors.has(row.__ID) ? "\u2014" : calculationValue(values.get(row.__ID)) }),
571
+ /* @__PURE__ */ jsxRuntime.jsx(
572
+ "td",
573
+ {
574
+ className: errors.has(row.__ID) ? "text-destructive" : "text-muted-foreground",
575
+ children: errors.get(row.__ID) ?? (values.get(row.__ID) == null ? "Missing value" : "Valid")
576
+ }
577
+ )
578
+ ] }, row.__ID)) })
579
+ ] }),
580
+ !rows.length && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "p-4 text-sm text-muted-foreground", children: errorsOnly ? "No failed rows." : "No loaded rows." })
581
+ ] }),
582
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 mt-2 text-xs text-muted-foreground", children: [
583
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
584
+ rows.length ? start + 1 : 0,
585
+ "\u2013",
586
+ Math.min(start + 5, rows.length),
587
+ " of",
588
+ " ",
589
+ rows.length.toLocaleString(),
590
+ " rows"
591
+ ] }),
592
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-1", children: [
593
+ /* @__PURE__ */ jsxRuntime.jsx(
594
+ chunk5B23JVH3_cjs.Button,
595
+ {
596
+ type: "button",
597
+ variant: "ghost",
598
+ size: "sm",
599
+ disabled: currentPage === 0,
600
+ onClick: () => setPage(currentPage - 1),
601
+ children: "Previous rows"
602
+ }
603
+ ),
604
+ /* @__PURE__ */ jsxRuntime.jsx(
605
+ chunk5B23JVH3_cjs.Button,
606
+ {
607
+ type: "button",
608
+ variant: "ghost",
609
+ size: "sm",
610
+ disabled: start + 5 >= rows.length,
611
+ onClick: () => setPage(currentPage + 1),
612
+ children: "Next rows"
613
+ }
614
+ )
615
+ ] })
616
+ ] })
617
+ ] });
618
+ }
619
+ function CalculationTrace({
620
+ calculations,
621
+ manager,
622
+ row,
623
+ root,
624
+ selected,
625
+ draftFields,
626
+ previewing,
627
+ onSelect
628
+ }) {
629
+ const fieldProfiles = chunk5B23JVH3_cjs.useDataLayer((state) => state.fieldProfiles);
630
+ const getColumnData = chunk5B23JVH3_cjs.useDataLayer((state) => state.getColumnData);
631
+ const byName = new Map(
632
+ calculations.map((calc) => [calc.resultColumnName, calc])
633
+ );
634
+ const dependents = dependentCalculations(calculations, selected);
635
+ const node = (name, path) => {
636
+ if (path.has(name)) return null;
637
+ const calc = byName.get(name);
638
+ const value = calc ? manager.executeCalculation(calc).get(row.__ID) : row[name];
639
+ const error = calc && manager.getErrors(name).get(row.__ID);
640
+ const nextPath = /* @__PURE__ */ new Set([...path, name]);
641
+ return /* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
642
+ calc ? /* @__PURE__ */ jsxRuntime.jsx(chunk5B23JVH3_cjs.ActionTooltip, { content: calc.expression.rawInput, children: /* @__PURE__ */ jsxRuntime.jsxs(
643
+ "button",
644
+ {
645
+ type: "button",
646
+ className: "eda-calc-node",
647
+ "aria-pressed": name === selected,
648
+ onClick: () => onSelect(name),
649
+ children: [
650
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "eda-calc-symbol", "aria-hidden": "true", children: "\u0192x" }),
651
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 flex-1", children: [
652
+ /* @__PURE__ */ jsxRuntime.jsx(
653
+ FieldMetadata,
654
+ {
655
+ profile: resolveFieldProfile(
656
+ name,
657
+ fieldProfiles,
658
+ getColumnData
659
+ ),
660
+ label: name,
661
+ compact: true,
662
+ className: "min-w-0"
663
+ }
664
+ ),
665
+ draftFields.includes(name) && /* @__PURE__ */ jsxRuntime.jsx("small", { className: "block text-xs font-normal text-muted-foreground", children: name === selected && previewing ? "Draft preview" : "Draft kept" })
666
+ ] }),
667
+ /* @__PURE__ */ jsxRuntime.jsx(chunk5B23JVH3_cjs.ActionTooltip, { content: error || String(value ?? "Missing"), children: /* @__PURE__ */ jsxRuntime.jsx(
668
+ "span",
669
+ {
670
+ className: error ? "text-destructive" : "eda-calc-node-value",
671
+ children: error ? "Error" : calculationValue(value)
672
+ }
673
+ ) })
674
+ ]
675
+ }
676
+ ) }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "eda-calc-source", children: [
677
+ /* @__PURE__ */ jsxRuntime.jsx(
678
+ FieldMetadata,
679
+ {
680
+ profile: resolveFieldProfile(name, fieldProfiles, getColumnData),
681
+ label: name,
682
+ compact: true,
683
+ className: "min-w-0 flex-1"
684
+ }
685
+ ),
686
+ /* @__PURE__ */ jsxRuntime.jsx(chunk5B23JVH3_cjs.ActionTooltip, { content: String(value ?? "Missing"), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "eda-calc-node-value", children: calculationValue(value) }) })
687
+ ] }),
688
+ calc && calc.expression.dependencies.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("ul", { children: calc.expression.dependencies.map((field) => node(field, nextPath)) })
689
+ ] }, name);
690
+ };
691
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "eda-calc-trace", children: [
692
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "eda-calc-section-heading", children: [
693
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "How this value is made" }),
694
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
695
+ "Row ",
696
+ row.__ID + 1
697
+ ] })
698
+ ] }),
699
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "eda-calc-help", children: "Follow the inputs below. Select a calculation to inspect or edit that step." }),
700
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "eda-calc-tree", "aria-label": "Calculation dependency chain", children: node(root, /* @__PURE__ */ new Set()) }),
701
+ root !== selected && /* @__PURE__ */ jsxRuntime.jsxs(
702
+ "button",
703
+ {
704
+ className: "eda-calc-text-button",
705
+ type: "button",
706
+ onClick: () => onSelect(root),
707
+ children: [
708
+ "Return to ",
709
+ root
710
+ ]
711
+ }
712
+ ),
713
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "eda-calc-section-heading mt-6", children: [
714
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Used by" }),
715
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
716
+ dependents.length,
717
+ " calculations"
718
+ ] })
719
+ ] }),
720
+ dependents.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "eda-calc-link-list", children: dependents.map((calc) => /* @__PURE__ */ jsxRuntime.jsxs(
721
+ "button",
722
+ {
723
+ type: "button",
724
+ onClick: () => onSelect(calc.resultColumnName),
725
+ children: [
726
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "eda-calc-symbol", children: "\u0192x" }),
727
+ calc.resultColumnName
728
+ ]
729
+ },
730
+ calc.resultColumnName
731
+ )) }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "eda-calc-help", children: "No calculations depend on this field." })
732
+ ] });
733
+ }
734
+ var EditorContext = react.createContext(null);
735
+ var useCalculationEditor = () => react.useContext(EditorContext);
736
+ function CalculationEditorProvider({
737
+ children
738
+ }) {
739
+ const data = chunk5B23JVH3_cjs.useDataLayer((state) => state.data);
740
+ const calculations = chunk5B23JVH3_cjs.useDataLayer((state) => state.calculations);
741
+ const savedManager = chunk5B23JVH3_cjs.useDataLayer((state) => state.calculationManager);
742
+ const charts = chunk5B23JVH3_cjs.useDataLayer((state) => state.charts);
743
+ const getColumnNames = chunk5B23JVH3_cjs.useDataLayer((state) => state.getColumnNames);
744
+ const getFieldLabel = chunk5B23JVH3_cjs.useDataLayer((state) => state.getFieldLabel);
745
+ const addCalculation = chunk5B23JVH3_cjs.useDataLayer((state) => state.addCalculation);
746
+ const updateCalculation = chunk5B23JVH3_cjs.useDataLayer((state) => state.updateCalculation);
747
+ const [visible, setVisible] = react.useState(false);
748
+ const [selected, setSelected] = react.useState();
749
+ const [root, setRoot] = react.useState();
750
+ const [rowId, setRowId] = react.useState(0);
751
+ const [drafts, setDrafts] = react.useState({});
752
+ const [preview, setPreview] = react.useState();
753
+ const [saveError, setSaveError] = react.useState("");
754
+ const [savedMessage, setSavedMessage] = react.useState("");
755
+ const returnFocus = react.useRef(null);
756
+ const open = react.useCallback(
757
+ (name, row, target) => {
758
+ returnFocus.current = target ?? document.activeElement;
759
+ setSelected(name);
760
+ setRoot(name);
761
+ setRowId(row ?? 0);
762
+ setSaveError("");
763
+ setSavedMessage("");
764
+ setVisible(true);
765
+ },
766
+ []
767
+ );
768
+ const editor = react.useMemo(() => ({ open }), [open]);
769
+ react.useEffect(() => {
770
+ setDrafts({});
771
+ setVisible(false);
772
+ }, [data]);
773
+ const key = selected ?? "";
774
+ const calculation = calculations.find(
775
+ (calc) => calc.resultColumnName === selected
776
+ );
777
+ const draft = Object.hasOwn(drafts, key) ? drafts[key] : {
778
+ name: calculation?.resultColumnName ?? "",
779
+ expression: calculation?.expression.rawInput ?? ""
780
+ };
781
+ const dirty = draft.name !== (calculation?.resultColumnName ?? "") || draft.expression !== (calculation?.expression.rawInput ?? "");
782
+ const previewKey = JSON.stringify([selected, draft.name, draft.expression]);
783
+ react.useEffect(() => {
784
+ if (!visible) return;
785
+ setPreview(void 0);
786
+ const timer = setTimeout(() => {
787
+ if (!draft.name.trim() || !draft.expression.trim()) {
788
+ setPreview({ key: previewKey });
789
+ return;
790
+ }
791
+ try {
792
+ const candidate = {
793
+ resultColumnName: draft.name.trim(),
794
+ expression: chunkSKXGYJJG_cjs.parseExpression(draft.expression)
795
+ };
796
+ const manager = new chunk5B23JVH3_cjs.CalculationManager(data, [
797
+ ...calculations.filter((calc) => calc.resultColumnName !== selected),
798
+ candidate
799
+ ]);
800
+ manager.executeCalculation(candidate);
801
+ setPreview({ key: previewKey, manager, calculation: candidate });
802
+ } catch (error) {
803
+ setPreview({
804
+ key: previewKey,
805
+ error: error instanceof Error ? error.message : String(error)
806
+ });
807
+ }
808
+ }, 220);
809
+ return () => clearTimeout(timer);
810
+ }, [visible, previewKey, data, calculations]);
811
+ const current = preview?.key === previewKey ? preview : void 0;
812
+ const checking = !current && Boolean(draft.name.trim() && draft.expression.trim());
813
+ const dependents = dependentCalculations(
814
+ calculations,
815
+ selected ?? draft.name
816
+ );
817
+ const affected = /* @__PURE__ */ new Set([
818
+ selected ?? draft.name,
819
+ ...dependents.map((calc) => calc.resultColumnName)
820
+ ]);
821
+ const usedViews = charts.filter(
822
+ (chart) => [
823
+ ...chunk5B23JVH3_cjs.getChartFields(chart),
824
+ ...chart.filters.map((filter) => filter.field),
825
+ chart.facet?.rowVariable,
826
+ chart.facet?.type === "grid" ? chart.facet.columnVariable : void 0
827
+ ].some((field) => field && affected.has(field))
828
+ );
829
+ const nameLocked = Boolean(
830
+ calculation && (dependents.length || usedViews.length)
831
+ );
832
+ const canApply = Boolean(dirty && current?.manager && current.calculation);
833
+ const errorCount = current?.manager?.getErrors(draft.name.trim()).size ?? 0;
834
+ const removeDraft = () => setDrafts((previous) => {
835
+ const next = { ...previous };
836
+ delete next[key];
837
+ return next;
838
+ });
839
+ const apply = async () => {
840
+ if (!canApply || !current?.calculation) return;
841
+ try {
842
+ if (selected) updateCalculation(selected, current.calculation);
843
+ else await addCalculation(current.calculation);
844
+ removeDraft();
845
+ const name = current.calculation.resultColumnName;
846
+ setSelected(name);
847
+ if (!root || root === selected) setRoot(name);
848
+ setSaveError("");
849
+ setSavedMessage(
850
+ "Applied. All dependent calculations and views are up to date."
851
+ );
852
+ } catch (error) {
853
+ setSaveError(error instanceof Error ? error.message : String(error));
854
+ }
855
+ };
856
+ const traceManager = current?.manager ?? savedManager;
857
+ const traceCalculations = traceManager.getCalculations();
858
+ const traceRoot = root === selected ? current?.calculation?.resultColumnName ?? root : root ?? current?.calculation?.resultColumnName;
859
+ const selectedRow = data.find((row) => row.__ID === rowId) ?? data[0];
860
+ const status = saveError || current?.error ? /* @__PURE__ */ jsxRuntime.jsxs("p", { role: "alert", className: "eda-calc-error", children: [
861
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CircleAlert, { "aria-hidden": "true", size: 16 }),
862
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: saveError || current?.error })
863
+ ] }) : checking ? /* @__PURE__ */ jsxRuntime.jsxs("p", { role: "status", children: [
864
+ "Checking formula against ",
865
+ data.length.toLocaleString(),
866
+ " loaded rows\u2026"
867
+ ] }) : current?.manager ? /* @__PURE__ */ jsxRuntime.jsxs(
868
+ "p",
869
+ {
870
+ role: "status",
871
+ className: errorCount ? "eda-calc-error" : "eda-calc-valid",
872
+ children: [
873
+ errorCount ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CircleAlert, { size: 16, "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle2, { size: 16, "aria-hidden": "true" }),
874
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
875
+ errorCount ? errorCount.toLocaleString() + " of " + data.length.toLocaleString() + " rows failed. Valid rows remain available." : "Valid for all " + data.length.toLocaleString() + " rows.",
876
+ savedMessage && " " + savedMessage
877
+ ] })
878
+ ]
879
+ }
880
+ ) : /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Enter a field name and formula to preview results." });
881
+ return /* @__PURE__ */ jsxRuntime.jsxs(EditorContext.Provider, { value: editor, children: [
882
+ children,
883
+ /* @__PURE__ */ jsxRuntime.jsx(chunkMJROUIDZ_cjs.Dialog, { open: visible, onOpenChange: setVisible, children: /* @__PURE__ */ jsxRuntime.jsxs(
884
+ chunkMJROUIDZ_cjs.DialogContent,
885
+ {
886
+ className: "eda-calc-editor",
887
+ onCloseAutoFocus: (event) => {
888
+ event.preventDefault();
889
+ if (returnFocus.current?.isConnected) returnFocus.current.focus();
890
+ },
891
+ children: [
892
+ /* @__PURE__ */ jsxRuntime.jsxs(chunkMJROUIDZ_cjs.DialogHeader, { className: "eda-calc-editor-header", children: [
893
+ /* @__PURE__ */ jsxRuntime.jsxs(chunkMJROUIDZ_cjs.DialogTitle, { children: [
894
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "eda-calc-symbol mr-2", children: "\u0192x" }),
895
+ selected ? "Inspect calculation" : "New calculation"
896
+ ] }),
897
+ /* @__PURE__ */ jsxRuntime.jsx(chunkMJROUIDZ_cjs.DialogDescription, { children: root ? "Following " + root + ". Preview a change before applying it to the analysis." : "Turn source fields into values you can reuse in any view." })
898
+ ] }),
899
+ /* @__PURE__ */ jsxRuntime.jsx(
900
+ CalculationForm,
901
+ {
902
+ draft,
903
+ onChange: (next) => {
904
+ setDrafts((previous) => ({ ...previous, [key]: next }));
905
+ setSaveError("");
906
+ setSavedMessage("");
907
+ },
908
+ fields: getColumnNames().filter((field) => field !== selected),
909
+ calculatedFields: calculations.map((calc) => calc.resultColumnName),
910
+ editing: Boolean(calculation),
911
+ nameLocked,
912
+ checking,
913
+ canApply,
914
+ dirty,
915
+ status,
916
+ impact: dependents.length + " dependent calculations \xB7 " + usedViews.length + " affected views",
917
+ onApply: apply,
918
+ onDiscard: () => {
919
+ removeDraft();
920
+ setSaveError("");
921
+ setSavedMessage("");
922
+ },
923
+ onClose: () => setVisible(false),
924
+ preview: current?.manager && current.calculation ? /* @__PURE__ */ jsxRuntime.jsx(
925
+ CalculationPreview,
926
+ {
927
+ calculation: current.calculation,
928
+ manager: current.manager,
929
+ savedManager,
930
+ data,
931
+ selectedRow: selectedRow?.__ID ?? 0,
932
+ onSelectRow: setRowId,
933
+ changed: dirty
934
+ },
935
+ key
936
+ ) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "eda-calc-preview-empty", children: [
937
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "See the result before you apply" }),
938
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Inputs, saved values, and draft results appear here once the formula is valid." })
939
+ ] }),
940
+ trace: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
941
+ dirty && !current?.manager && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "eda-calc-help mb-3", children: "The chain shows saved values until this draft can be previewed." }),
942
+ traceRoot && selectedRow ? /* @__PURE__ */ jsxRuntime.jsx(
943
+ CalculationTrace,
944
+ {
945
+ calculations: traceCalculations,
946
+ manager: traceManager,
947
+ row: selectedRow,
948
+ root: traceRoot,
949
+ selected: selected ?? draft.name,
950
+ draftFields: Object.keys(drafts),
951
+ previewing: Boolean(current?.manager),
952
+ onSelect: (field) => {
953
+ if (root && dependentCalculations(calculations, root).some(
954
+ (calc) => calc.resultColumnName === field
955
+ ))
956
+ setRoot(field);
957
+ setSelected(field);
958
+ setSaveError("");
959
+ setSavedMessage("");
960
+ }
961
+ }
962
+ ) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "eda-calc-help", children: "Add a formula to see how its fields connect." }),
963
+ usedViews.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "mt-6", children: [
964
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-medium", children: "Used in views" }),
965
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "eda-calc-view-list", children: usedViews.map((chart) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: chunk5B23JVH3_cjs.getChartTitle(chart, getFieldLabel) }, chart.id)) })
966
+ ] })
967
+ ] })
968
+ },
969
+ key
970
+ )
971
+ ]
972
+ }
973
+ ) })
974
+ ] });
975
+ }
976
+ function CalculatedFieldBadge({
977
+ field,
978
+ showName = false,
979
+ rowId = 0,
980
+ children,
981
+ hoverOpen = true,
982
+ side = "bottom"
983
+ }) {
984
+ const calculations = chunk5B23JVH3_cjs.useDataLayer((state) => state.calculations) ?? [];
985
+ const manager = chunk5B23JVH3_cjs.useDataLayer((state) => state.calculationManager);
986
+ const data = chunk5B23JVH3_cjs.useDataLayer((state) => state.data);
987
+ const fieldProfiles = chunk5B23JVH3_cjs.useDataLayer((state) => state.fieldProfiles);
988
+ const getColumnData = chunk5B23JVH3_cjs.useDataLayer((state) => state.getColumnData);
989
+ const editor = useCalculationEditor();
990
+ const calc = calculations.find((item) => item.resultColumnName === field);
991
+ const [open, setOpen] = react.useState(false);
992
+ const pinned = react.useRef(false);
993
+ const restoreFocus = react.useRef(false);
994
+ const timer = react.useRef(void 0);
995
+ const trigger = react.useRef(null);
996
+ const cancelTimer = () => clearTimeout(timer.current);
997
+ react.useEffect(() => () => clearTimeout(timer.current), []);
998
+ if (!calc || !editor) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
999
+ const leave = () => {
1000
+ cancelTimer();
1001
+ if (!pinned.current) timer.current = setTimeout(() => setOpen(false), 180);
1002
+ };
1003
+ const errors = open ? manager.getErrors(field) : /* @__PURE__ */ new Map();
1004
+ const value = open ? manager.executeCalculation(calc).get(rowId) : void 0;
1005
+ const profile = open ? resolveFieldProfile(field, fieldProfiles, getColumnData) : void 0;
1006
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1007
+ chunkMJROUIDZ_cjs.Popover,
1008
+ {
1009
+ open,
1010
+ onOpenChange: (next) => {
1011
+ setOpen(next);
1012
+ if (!next) pinned.current = false;
1013
+ },
1014
+ children: [
1015
+ /* @__PURE__ */ jsxRuntime.jsx(chunkMJROUIDZ_cjs.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1016
+ "button",
1017
+ {
1018
+ ref: trigger,
1019
+ type: "button",
1020
+ className: children !== void 0 ? "eda-calc-cell" : "eda-calc-badge",
1021
+ "aria-label": "Inspect " + field + " calculation" + (children !== void 0 ? ", row " + (rowId + 1) : ""),
1022
+ onPointerEnter: (event) => {
1023
+ if (!hoverOpen || event.pointerType === "touch" || event.buttons)
1024
+ return;
1025
+ cancelTimer();
1026
+ timer.current = setTimeout(() => setOpen(true), 250);
1027
+ },
1028
+ onPointerLeave: leave,
1029
+ onClick: (event) => {
1030
+ event.preventDefault();
1031
+ event.stopPropagation();
1032
+ cancelTimer();
1033
+ pinned.current = true;
1034
+ restoreFocus.current = true;
1035
+ setOpen(true);
1036
+ },
1037
+ children: children !== void 0 ? children : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1038
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "eda-calc-symbol", "aria-hidden": "true", children: "\u0192x" }),
1039
+ showName && /* @__PURE__ */ jsxRuntime.jsx("span", { children: field })
1040
+ ] })
1041
+ }
1042
+ ) }),
1043
+ /* @__PURE__ */ jsxRuntime.jsxs(
1044
+ chunkMJROUIDZ_cjs.PopoverContent,
1045
+ {
1046
+ className: "eda-calc-peek",
1047
+ align: "start",
1048
+ side,
1049
+ collisionPadding: 12,
1050
+ onPointerEnter: cancelTimer,
1051
+ onPointerLeave: leave,
1052
+ onOpenAutoFocus: (event) => {
1053
+ if (!pinned.current) event.preventDefault();
1054
+ },
1055
+ onCloseAutoFocus: (event) => {
1056
+ event.preventDefault();
1057
+ if (restoreFocus.current) trigger.current?.focus();
1058
+ restoreFocus.current = false;
1059
+ },
1060
+ children: [
1061
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "eda-calc-section-heading", children: [
1062
+ /* @__PURE__ */ jsxRuntime.jsx(FieldMetadata, { profile, label: field, compact: true }),
1063
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Calculated field" })
1064
+ ] }),
1065
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { children: calc.expression.rawInput }),
1066
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "eda-calc-help", children: [
1067
+ "Uses",
1068
+ " ",
1069
+ calc.expression.dependencies.length ? calc.expression.dependencies.join(", ") : "constant values",
1070
+ "."
1071
+ ] }),
1072
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "eda-calc-peek-result", children: [
1073
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1074
+ "Row ",
1075
+ rowId + 1
1076
+ ] }),
1077
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: errors.get(rowId) ?? calculationValue(value) })
1078
+ ] }),
1079
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "eda-calc-help", children: [
1080
+ errors.size ? errors.size.toLocaleString() + " of " + data.length.toLocaleString() + " rows failed" : "All " + data.length.toLocaleString() + " rows valid",
1081
+ " ",
1082
+ "\xB7 ",
1083
+ dependentCalculations(calculations, field).length,
1084
+ " dependent calculations"
1085
+ ] }),
1086
+ /* @__PURE__ */ jsxRuntime.jsx(
1087
+ chunk5B23JVH3_cjs.Button,
1088
+ {
1089
+ type: "button",
1090
+ className: "mt-4 w-full",
1091
+ onClick: () => {
1092
+ setOpen(false);
1093
+ pinned.current = false;
1094
+ restoreFocus.current = false;
1095
+ editor.open(field, rowId, trigger.current);
1096
+ },
1097
+ children: "Inspect chain & edit"
1098
+ }
1099
+ )
1100
+ ]
1101
+ }
1102
+ )
1103
+ ]
1104
+ }
1105
+ );
1106
+ }
1107
+
1108
+ exports.CalculatedFieldBadge = CalculatedFieldBadge;
1109
+ exports.CalculationEditorProvider = CalculationEditorProvider;
1110
+ exports.FieldMetadata = FieldMetadata;
1111
+ exports.Tabs = Tabs;
1112
+ exports.TabsContent = TabsContent;
1113
+ exports.TabsList = TabsList;
1114
+ exports.TabsTrigger = TabsTrigger;
1115
+ exports.dependentCalculations = dependentCalculations;
1116
+ exports.resolveFieldProfile = resolveFieldProfile;
1117
+ exports.useCalculationEditor = useCalculationEditor;
1118
+ //# sourceMappingURL=chunk-C7GQT65O.cjs.map
1119
+ //# sourceMappingURL=chunk-C7GQT65O.cjs.map