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,1071 @@
1
+ import { FieldInspector } from './chunk-LBTZVERO.js';
2
+ import { DEFAULT_CHART_SETTINGS } from './chunk-CJQQFNEY.js';
3
+ import { applyFilter } from './chunk-YXPAWUXO.js';
4
+ import { CalculatedFieldBadge, FieldMetadata, resolveFieldProfile } from './chunk-NDF5LCST.js';
5
+ import { useColumnNames, multi_select_default } from './chunk-SWLNKY6R.js';
6
+ import { Label, Input, Popover, PopoverTrigger, PopoverContent } from './chunk-MJPDJ5AS.js';
7
+ import { useDataLayer, getChartSummary, buildFieldProfile, Button, ActionTooltip, cn, categoryLabel, categoryIncludes, categoryKey, categoryEqual } from './chunk-JNO7OXCC.js';
8
+ import { Table, ChevronUp, ChevronDown, Settings2, Filter, X, Columns3, Search, Download, FilterX } from 'lucide-react';
9
+ import { useMemo, useRef, useState, useEffect } from 'react';
10
+ import { createPortal } from 'react-dom';
11
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
12
+
13
+ function TableHeader({ className, ...props }) {
14
+ return /* @__PURE__ */ jsx(
15
+ "thead",
16
+ {
17
+ "data-slot": "table-header",
18
+ className: cn("[&_tr]:border-b", className),
19
+ ...props
20
+ }
21
+ );
22
+ }
23
+ function TableBody({ className, ...props }) {
24
+ return /* @__PURE__ */ jsx(
25
+ "tbody",
26
+ {
27
+ "data-slot": "table-body",
28
+ className: cn("[&_tr:last-child]:border-0", className),
29
+ ...props
30
+ }
31
+ );
32
+ }
33
+ function TableRow({ className, ...props }) {
34
+ return /* @__PURE__ */ jsx(
35
+ "tr",
36
+ {
37
+ "data-slot": "table-row",
38
+ className: cn(
39
+ "hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
40
+ className
41
+ ),
42
+ ...props
43
+ }
44
+ );
45
+ }
46
+ function TableHead({ className, ...props }) {
47
+ return /* @__PURE__ */ jsx(
48
+ "th",
49
+ {
50
+ "data-slot": "table-head",
51
+ className: cn(
52
+ "text-muted-foreground h-10 px-2 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
53
+ className
54
+ ),
55
+ ...props
56
+ }
57
+ );
58
+ }
59
+ function TableCell({ className, ...props }) {
60
+ return /* @__PURE__ */ jsx(
61
+ "td",
62
+ {
63
+ "data-slot": "table-cell",
64
+ className: cn(
65
+ "p-0.5 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
66
+ className
67
+ ),
68
+ ...props
69
+ }
70
+ );
71
+ }
72
+
73
+ // src/components/charts/DataTable/filteredRows.ts
74
+ function getFilteredRows(data, liveItems, settings) {
75
+ const ids = new Set(
76
+ liveItems?.items.filter((item) => item.value > 0).map((item) => item.key)
77
+ );
78
+ const rows = data.filter((row) => ids.has(row.__ID));
79
+ const search = settings.globalSearch.toLowerCase();
80
+ const filtered = rows.filter((row) => {
81
+ if (search && !Object.entries(row).some(
82
+ ([field, value]) => field !== "__ID" && value !== null && value !== void 0 && String(value).toLowerCase().includes(search)
83
+ )) {
84
+ return false;
85
+ }
86
+ return settings.filters.every(
87
+ (filter) => applyFilter(row[filter.field], filter)
88
+ );
89
+ });
90
+ if (settings.sortBy) {
91
+ const field = settings.sortBy;
92
+ filtered.sort((a, b) => {
93
+ const comparison = compareValues(a[field], b[field]);
94
+ return settings.sortDirection === "desc" ? -comparison : comparison;
95
+ });
96
+ }
97
+ return filtered;
98
+ }
99
+ function isNumeric(value) {
100
+ if (typeof value === "number") {
101
+ return true;
102
+ }
103
+ if (typeof value !== "string") {
104
+ return false;
105
+ }
106
+ return !isNaN(Number(value)) && !isNaN(parseFloat(value));
107
+ }
108
+ function compareValues(a, b) {
109
+ if (a == null && b == null) return 0;
110
+ if (a === null || a === void 0) {
111
+ return 1;
112
+ }
113
+ if (b === null || b === void 0) {
114
+ return -1;
115
+ }
116
+ if (a === b) {
117
+ return 0;
118
+ }
119
+ const aStr = String(a);
120
+ const bStr = String(b);
121
+ if (isNumeric(a) && isNumeric(b)) {
122
+ return Number(a) - Number(b);
123
+ }
124
+ return aStr.localeCompare(bStr, void 0, { numeric: true });
125
+ }
126
+ function DataTableBody({
127
+ settings,
128
+ rows,
129
+ scrollTop = 0,
130
+ viewportHeight = 400
131
+ }) {
132
+ const calculations = useDataLayer((state) => state.calculations) ?? [];
133
+ const manager = useDataLayer((state) => state.calculationManager);
134
+ const data = useDataLayer((state) => state.data);
135
+ const formatFieldValue = useDataLayer((state) => state.formatFieldValue);
136
+ useDataLayer((state) => state.fieldSettings);
137
+ const format = formatFieldValue ?? ((_, value) => String(value ?? "\u2014"));
138
+ const liveItems = useDataLayer((state) => state.getLiveItems(settings));
139
+ const filteredByColumns = useMemo(
140
+ () => rows ?? getFilteredRows(data, liveItems, settings),
141
+ [rows, data, liveItems, settings]
142
+ );
143
+ const sortedData = filteredByColumns;
144
+ const start = Math.min(
145
+ Math.max(0, Math.floor((scrollTop - 36) / 30) - 6),
146
+ Math.max(0, sortedData.length - 1)
147
+ );
148
+ const end = Math.min(
149
+ sortedData.length,
150
+ start + Math.ceil(viewportHeight / 30) + 12
151
+ );
152
+ const visibleRows = sortedData.slice(start, end);
153
+ const spacer = (count, key) => count > 0 && /* @__PURE__ */ jsx("tr", { "aria-hidden": "true", className: "eda-table-spacer", children: /* @__PURE__ */ jsx(
154
+ "td",
155
+ {
156
+ colSpan: settings.columns.length,
157
+ style: { height: count * 30, padding: 0, border: 0 }
158
+ }
159
+ ) }, key);
160
+ return /* @__PURE__ */ jsxs(TableBody, { children: [
161
+ spacer(start, "before"),
162
+ visibleRows.length > 0 ? visibleRows.map((row, index) => /* @__PURE__ */ jsx(TableRow, { "aria-rowindex": start + index + 2, children: settings.columns.map((column, index2) => /* @__PURE__ */ jsx(
163
+ TableCell,
164
+ {
165
+ className: index2 === 0 ? "sticky left-0 z-10 bg-background" : "",
166
+ style: {
167
+ width: column.width,
168
+ textAlign: typeof row[column.field] === "number" ? "right" : "left"
169
+ },
170
+ children: calculations.some(
171
+ (calc) => calc.resultColumnName === column.field
172
+ ) ? /* @__PURE__ */ jsx(CalculatedFieldBadge, { field: column.field, rowId: row.__ID, children: manager?.getErrors(column.field).has(row.__ID) ? "Error" : format(column.field, row[column.field]) }) : format(column.field, row[column.field])
173
+ },
174
+ column.id
175
+ )) }, String(row.__ID))) : /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, { colSpan: settings.columns.length, className: "text-center", children: "No rows match the current filters." }) }),
176
+ spacer(sortedData.length - end, "after")
177
+ ] });
178
+ }
179
+ var MAX_VALUE_FILTER_OPTIONS = 10;
180
+ function ColumnFilter({
181
+ columnId,
182
+ columnLabel,
183
+ profile,
184
+ filter,
185
+ onChange,
186
+ onClear,
187
+ local = false
188
+ }) {
189
+ const updateRange = (type, key, raw) => {
190
+ const current = filter?.type === type ? filter : { type, field: profile.name };
191
+ const value = raw === "" ? void 0 : type === "range" ? Number(raw) : raw;
192
+ if (value !== void 0 && type === "range" && !Number.isFinite(value)) {
193
+ return;
194
+ }
195
+ const next = { ...current, [key]: value };
196
+ if ((next.type === "range" || next.type === "date-range") && next.min === void 0 && next.max === void 0) {
197
+ onChange(columnId);
198
+ } else {
199
+ onChange(columnId, next);
200
+ }
201
+ };
202
+ const updateValues = (value, checked) => {
203
+ const selected = filter?.type === "value" ? filter.values : [];
204
+ const next = checked ? [...selected, value] : selected.filter((item) => !categoryEqual(item, value));
205
+ onChange(
206
+ columnId,
207
+ next.length > 0 ? { type: "value", field: profile.name, values: next } : void 0
208
+ );
209
+ };
210
+ const textFilter = filter?.type === "text" ? filter : { type: "text", field: profile.name, operator: "contains", value: "" };
211
+ const rangeFilter = filter?.type === "range" ? filter : void 0;
212
+ const dateFilter = filter?.type === "date-range" ? filter : void 0;
213
+ const lowCardinality = profile.dataType === "boolean" || profile.dataType === "categorical" && profile.uniqueCount <= MAX_VALUE_FILTER_OPTIONS;
214
+ const updateText = (next) => onChange(columnId, next.value === "" ? void 0 : next);
215
+ return /* @__PURE__ */ jsxs(
216
+ "div",
217
+ {
218
+ className: "grid w-64 max-w-full gap-3 text-sm",
219
+ onClick: (event) => event.stopPropagation(),
220
+ children: [
221
+ /* @__PURE__ */ jsxs("div", { children: [
222
+ /* @__PURE__ */ jsxs("h3", { className: "font-semibold", children: [
223
+ "Filter ",
224
+ columnLabel
225
+ ] }),
226
+ /* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-muted-foreground", children: [
227
+ local ? "Filters only this table." : "Filters all charts and rows.",
228
+ " ",
229
+ "Changes apply immediately."
230
+ ] })
231
+ ] }),
232
+ lowCardinality ? /* @__PURE__ */ jsxs("div", { className: "flex max-h-56 flex-col gap-2 overflow-auto py-1", children: [
233
+ (profile.categories?.distribution ?? []).map(({ value }) => {
234
+ const label = categoryLabel(value);
235
+ return /* @__PURE__ */ jsxs(
236
+ "label",
237
+ {
238
+ className: "flex items-center gap-2 text-sm",
239
+ children: [
240
+ /* @__PURE__ */ jsx(
241
+ "input",
242
+ {
243
+ type: "checkbox",
244
+ checked: filter?.type === "value" && categoryIncludes(filter.values, value),
245
+ onChange: (event) => updateValues(value, event.currentTarget.checked)
246
+ }
247
+ ),
248
+ label
249
+ ]
250
+ },
251
+ categoryKey(value)
252
+ );
253
+ }),
254
+ profile.nullCount > 0 && /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm", children: [
255
+ /* @__PURE__ */ jsx(
256
+ "input",
257
+ {
258
+ type: "checkbox",
259
+ checked: filter?.type === "value" && filter.values.some((item) => item == null),
260
+ onChange: (event) => updateValues(null, event.currentTarget.checked)
261
+ }
262
+ ),
263
+ "Missing"
264
+ ] })
265
+ ] }) : profile.dataType === "numeric" ? /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
266
+ /* @__PURE__ */ jsxs("label", { className: "grid gap-1 text-xs", children: [
267
+ "Minimum (inclusive)",
268
+ /* @__PURE__ */ jsx(
269
+ Input,
270
+ {
271
+ type: "number",
272
+ "aria-label": `Minimum ${columnLabel}`,
273
+ placeholder: "Min",
274
+ value: rangeFilter?.min ?? "",
275
+ onChange: (event) => updateRange("range", "min", event.target.value),
276
+ className: "h-8 w-full"
277
+ }
278
+ )
279
+ ] }),
280
+ /* @__PURE__ */ jsxs("label", { className: "grid gap-1 text-xs", children: [
281
+ "Maximum (inclusive)",
282
+ /* @__PURE__ */ jsx(
283
+ Input,
284
+ {
285
+ type: "number",
286
+ "aria-label": `Maximum ${columnLabel}`,
287
+ placeholder: "Max",
288
+ value: rangeFilter?.max ?? "",
289
+ onChange: (event) => updateRange("range", "max", event.target.value),
290
+ className: "h-8 w-full"
291
+ }
292
+ )
293
+ ] })
294
+ ] }) : profile.dataType === "datetime" ? /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
295
+ /* @__PURE__ */ jsxs("label", { className: "grid gap-1 text-xs", children: [
296
+ "From (inclusive)",
297
+ /* @__PURE__ */ jsx(
298
+ Input,
299
+ {
300
+ type: "date",
301
+ "aria-label": `Start date ${columnLabel}`,
302
+ value: dateFilter?.min ?? "",
303
+ onInput: (event) => updateRange("date-range", "min", event.currentTarget.value),
304
+ className: "h-8 w-full"
305
+ }
306
+ )
307
+ ] }),
308
+ /* @__PURE__ */ jsxs("label", { className: "grid gap-1 text-xs", children: [
309
+ "Through (inclusive)",
310
+ /* @__PURE__ */ jsx(
311
+ Input,
312
+ {
313
+ type: "date",
314
+ "aria-label": `End date ${columnLabel}`,
315
+ value: dateFilter?.max ?? "",
316
+ onInput: (event) => updateRange("date-range", "max", event.currentTarget.value),
317
+ className: "h-8 w-full"
318
+ }
319
+ )
320
+ ] })
321
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
322
+ /* @__PURE__ */ jsxs("label", { className: "grid gap-1 text-xs", children: [
323
+ "Match",
324
+ /* @__PURE__ */ jsxs(
325
+ "select",
326
+ {
327
+ "aria-label": `Text operator ${columnLabel}`,
328
+ value: textFilter.operator,
329
+ onChange: (event) => updateText({
330
+ ...textFilter,
331
+ operator: event.target.value
332
+ }),
333
+ className: "h-8 rounded-md border bg-transparent px-2 text-sm",
334
+ children: [
335
+ /* @__PURE__ */ jsx("option", { value: "contains", children: "Contains" }),
336
+ /* @__PURE__ */ jsx("option", { value: "equals", children: "Equals" }),
337
+ /* @__PURE__ */ jsx("option", { value: "startsWith", children: "Starts with" }),
338
+ /* @__PURE__ */ jsx("option", { value: "endsWith", children: "Ends with" })
339
+ ]
340
+ }
341
+ )
342
+ ] }),
343
+ /* @__PURE__ */ jsxs("label", { className: "grid gap-1 text-xs", children: [
344
+ "Text",
345
+ /* @__PURE__ */ jsx(
346
+ Input,
347
+ {
348
+ "aria-label": `Filter text ${columnLabel}`,
349
+ placeholder: `Filter ${columnLabel}...`,
350
+ value: textFilter.value,
351
+ onChange: (event) => updateText({ ...textFilter, value: event.target.value }),
352
+ className: "h-8 w-full"
353
+ }
354
+ )
355
+ ] })
356
+ ] }),
357
+ /* @__PURE__ */ jsxs(
358
+ Button,
359
+ {
360
+ variant: "ghost",
361
+ size: "sm",
362
+ className: "justify-self-start",
363
+ "aria-label": `Clear filter for ${columnLabel}`,
364
+ onClick: onClear,
365
+ children: [
366
+ /* @__PURE__ */ jsx(FilterX, { className: "h-4 w-4" }),
367
+ " Clear filter"
368
+ ]
369
+ }
370
+ )
371
+ ]
372
+ }
373
+ );
374
+ }
375
+ function DataTableHeader({
376
+ settings,
377
+ onSettingsChange,
378
+ onColumnResize,
379
+ localFilters = false
380
+ }) {
381
+ const { columns, sortBy, sortDirection, filters } = settings;
382
+ const updateChart = useDataLayer((state) => state.updateChart);
383
+ const update = onSettingsChange ?? ((next) => updateChart(settings.id, next));
384
+ const fieldProfiles = useDataLayer((state) => state.fieldProfiles) ?? [];
385
+ const calculations = useDataLayer((state) => state.calculations) ?? [];
386
+ const getFieldLabel = useDataLayer((state) => state.getFieldLabel);
387
+ const fieldSettings = useDataLayer((state) => state.fieldSettings);
388
+ const label = getFieldLabel ?? ((field) => field);
389
+ const getColumnData = useDataLayer((state) => state.getColumnData);
390
+ const nonce = useDataLayer((state) => state.nonce);
391
+ const profiles = useMemo(
392
+ () => [
393
+ ...fieldProfiles,
394
+ ...calculations.filter(
395
+ (calc) => columns.some((column) => column.field === calc.resultColumnName)
396
+ ).map(
397
+ (calc) => buildFieldProfile(
398
+ calc.resultColumnName,
399
+ getColumnData(calc.resultColumnName)
400
+ )
401
+ )
402
+ ],
403
+ [fieldProfiles, calculations, columns, getColumnData, nonce, fieldSettings]
404
+ );
405
+ const [activeFilter, setActiveFilter] = useState(null);
406
+ const [resizingColumn, setResizingColumn] = useState(null);
407
+ const [tempWidths, setTempWidths] = useState({});
408
+ const resizeCleanup = useRef(null);
409
+ useEffect(() => () => resizeCleanup.current?.(), []);
410
+ const handleSort = (field) => {
411
+ if (sortBy === field) {
412
+ update({
413
+ sortDirection: sortDirection === "asc" ? "desc" : "asc"
414
+ });
415
+ } else {
416
+ update({
417
+ sortBy: field,
418
+ sortDirection: "asc"
419
+ });
420
+ }
421
+ };
422
+ const handleFilterChange = (columnId, filter) => {
423
+ const column = columns.find((col) => col.id === columnId);
424
+ if (!column) {
425
+ return;
426
+ }
427
+ const newFilters = filters.filter((f) => f.field !== column.field);
428
+ if (filter) {
429
+ newFilters.push({ ...filter, field: column.field });
430
+ }
431
+ update({
432
+ filters: newFilters
433
+ });
434
+ };
435
+ const handleFilterClear = (columnId) => {
436
+ const column = columns.find((col) => col.id === columnId);
437
+ if (!column) {
438
+ return;
439
+ }
440
+ const newFilters = filters.filter((f) => f.field !== column.field);
441
+ update({
442
+ filters: newFilters
443
+ });
444
+ };
445
+ const handleResizeStart = (e, columnId) => {
446
+ e.preventDefault();
447
+ e.stopPropagation();
448
+ e.currentTarget.setPointerCapture?.(e.pointerId);
449
+ resizeCleanup.current?.();
450
+ setResizingColumn(columnId);
451
+ const startX = e.clientX;
452
+ const column = columns.find((col) => col.id === columnId);
453
+ const startWidth = e.currentTarget.parentElement?.getBoundingClientRect().width || column?.width || 120;
454
+ let width = startWidth;
455
+ const handleMouseMove = (e2) => {
456
+ width = Math.max(50, startWidth + e2.clientX - startX);
457
+ setTempWidths((prev) => ({ ...prev, [columnId]: width }));
458
+ onColumnResize?.(columnId, width);
459
+ };
460
+ const handleCancel = () => resizeCleanup.current?.();
461
+ const handleMouseUp = () => {
462
+ const newColumns = columns.map(
463
+ (col) => col.id === columnId ? { ...col, width } : col
464
+ );
465
+ update({ columns: newColumns });
466
+ resizeCleanup.current = null;
467
+ setResizingColumn(null);
468
+ setTempWidths({});
469
+ onColumnResize?.(columnId, null);
470
+ window.removeEventListener("pointermove", handleMouseMove);
471
+ window.removeEventListener("pointerup", handleMouseUp);
472
+ window.removeEventListener("pointercancel", handleCancel);
473
+ };
474
+ resizeCleanup.current = () => {
475
+ window.removeEventListener("pointermove", handleMouseMove);
476
+ window.removeEventListener("pointerup", handleMouseUp);
477
+ window.removeEventListener("pointercancel", handleCancel);
478
+ setResizingColumn(null);
479
+ setTempWidths({});
480
+ onColumnResize?.(columnId, null);
481
+ resizeCleanup.current = null;
482
+ };
483
+ window.addEventListener("pointermove", handleMouseMove);
484
+ window.addEventListener("pointerup", handleMouseUp);
485
+ window.addEventListener("pointercancel", handleCancel);
486
+ };
487
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsx(TableRow, { children: columns.map((column, index) => {
488
+ const profile = profiles.find(
489
+ (fieldProfile) => fieldProfile.name === column.field
490
+ ) ?? {
491
+ name: column.field,
492
+ dataType: "categorical",
493
+ totalCount: 0,
494
+ uniqueCount: 11,
495
+ nullCount: 0
496
+ };
497
+ const filter = filters.find(
498
+ (f) => f.field === column.field
499
+ );
500
+ return /* @__PURE__ */ jsxs(
501
+ TableHead,
502
+ {
503
+ className: `relative select-none ${index === 0 ? "sticky left-0 z-20 bg-background" : ""}`,
504
+ style: {
505
+ width: resizingColumn === column.id ? tempWidths[column.id] || column.width : column.width
506
+ },
507
+ "aria-sort": sortBy === column.field ? sortDirection === "asc" ? "ascending" : "descending" : "none",
508
+ children: [
509
+ /* @__PURE__ */ jsxs("div", { className: "eda-column-heading", children: [
510
+ /* @__PURE__ */ jsxs(
511
+ "button",
512
+ {
513
+ type: "button",
514
+ className: "flex min-w-0 flex-1 items-center gap-1 text-left",
515
+ "aria-label": `Sort by ${column.field}`,
516
+ onClick: () => handleSort(column.field),
517
+ children: [
518
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: label(column.field) }),
519
+ sortBy === column.field && (sortDirection === "asc" ? /* @__PURE__ */ jsx(ChevronUp, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4" }))
520
+ ]
521
+ }
522
+ ),
523
+ /* @__PURE__ */ jsxs("div", { className: "eda-column-actions", children: [
524
+ /* @__PURE__ */ jsx(CalculatedFieldBadge, { field: column.field }),
525
+ /* @__PURE__ */ jsx(FieldInspector, { field: column.field, children: /* @__PURE__ */ jsx(
526
+ Button,
527
+ {
528
+ variant: "ghost",
529
+ size: "icon",
530
+ className: "h-6 w-6",
531
+ "aria-label": `Inspect ${column.field}`,
532
+ children: /* @__PURE__ */ jsx(Settings2, { className: "h-3.5 w-3.5" })
533
+ }
534
+ ) }),
535
+ /* @__PURE__ */ jsxs(
536
+ Popover,
537
+ {
538
+ open: activeFilter === column.id,
539
+ onOpenChange: (open) => setActiveFilter(open ? column.id : null),
540
+ children: [
541
+ /* @__PURE__ */ jsx(ActionTooltip, { content: `Filter ${label(column.field)}`, children: /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
542
+ Button,
543
+ {
544
+ variant: "ghost",
545
+ size: "sm",
546
+ className: `eda-column-filter ${filter ? "is-active" : ""}`,
547
+ "aria-label": `Filter ${column.field}`,
548
+ "aria-expanded": activeFilter === column.id,
549
+ onClick: (event) => {
550
+ event.stopPropagation();
551
+ setActiveFilter(
552
+ activeFilter === column.id ? null : column.id
553
+ );
554
+ },
555
+ children: /* @__PURE__ */ jsx(Filter, { className: "h-4 w-4" })
556
+ }
557
+ ) }) }),
558
+ /* @__PURE__ */ jsx(
559
+ PopoverContent,
560
+ {
561
+ className: "w-auto max-w-[calc(100vw-24px)]",
562
+ align: "start",
563
+ collisionPadding: 12,
564
+ children: /* @__PURE__ */ jsx(
565
+ ColumnFilter,
566
+ {
567
+ local: localFilters,
568
+ columnId: column.id,
569
+ columnLabel: label(column.field),
570
+ profile,
571
+ filter,
572
+ onChange: handleFilterChange,
573
+ onClear: () => handleFilterClear(column.id)
574
+ }
575
+ )
576
+ }
577
+ )
578
+ ]
579
+ }
580
+ )
581
+ ] })
582
+ ] }),
583
+ /* @__PURE__ */ jsx(
584
+ "div",
585
+ {
586
+ role: "separator",
587
+ "aria-orientation": "vertical",
588
+ "aria-label": `Resize ${column.field} column`,
589
+ className: "eda-column-resize",
590
+ "data-resizing": resizingColumn === column.id,
591
+ tabIndex: 0,
592
+ "aria-valuemin": 50,
593
+ "aria-valuenow": tempWidths[column.id] ?? column.width ?? 120,
594
+ onKeyDown: (event) => {
595
+ if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") {
596
+ return;
597
+ }
598
+ event.preventDefault();
599
+ const width = Math.max(
600
+ 50,
601
+ event.currentTarget.parentElement.getBoundingClientRect().width + (event.key === "ArrowRight" ? 16 : -16)
602
+ );
603
+ update({
604
+ columns: columns.map(
605
+ (item) => item.id === column.id ? { ...item, width } : item
606
+ )
607
+ });
608
+ },
609
+ onPointerDown: (e) => handleResizeStart(e, column.id)
610
+ }
611
+ )
612
+ ]
613
+ },
614
+ column.id
615
+ );
616
+ }) }) }) });
617
+ }
618
+ function DataTableToolbar({
619
+ settings,
620
+ rows,
621
+ onSettingsChange,
622
+ compact = false,
623
+ localFilters = false
624
+ }) {
625
+ const [fieldSearch, setFieldSearch] = useState("");
626
+ const getColumnData = useDataLayer((state) => state.getColumnData);
627
+ const fieldProfiles = useDataLayer((state) => state.fieldProfiles);
628
+ const getFieldLabel = useDataLayer((state) => state.getFieldLabel);
629
+ const updateChart = useDataLayer((state) => state.updateChart);
630
+ const data = useDataLayer((state) => state.data);
631
+ const liveItems = useDataLayer((state) => state.getLiveItems(settings));
632
+ const getColumnNames = useDataLayer((state) => state.getColumnNames);
633
+ const filteredData = rows ?? getFilteredRows(data, liveItems, settings);
634
+ const handleSearch = (globalSearch) => {
635
+ if (onSettingsChange) onSettingsChange({ globalSearch });
636
+ else updateChart(settings.id, { globalSearch });
637
+ };
638
+ const handleExport = () => {
639
+ const csvContent = toTableCsv(
640
+ filteredData,
641
+ settings.columns.map((column) => column.field)
642
+ );
643
+ const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
644
+ const link = document.createElement("a");
645
+ const url = URL.createObjectURL(blob);
646
+ link.setAttribute("href", url);
647
+ link.setAttribute(
648
+ "download",
649
+ `table-export-${(/* @__PURE__ */ new Date()).toISOString()}.csv`
650
+ );
651
+ link.style.visibility = "hidden";
652
+ document.body.appendChild(link);
653
+ link.click();
654
+ document.body.removeChild(link);
655
+ URL.revokeObjectURL(url);
656
+ };
657
+ const search = /* @__PURE__ */ jsxs("div", { className: "eda-table-search relative min-w-0", children: [
658
+ /* @__PURE__ */ jsx(
659
+ Input,
660
+ {
661
+ className: "h-8 text-xs",
662
+ placeholder: "Search this table\u2026",
663
+ "aria-label": "Search table",
664
+ value: settings.globalSearch,
665
+ onChange: (event) => handleSearch(event.target.value)
666
+ }
667
+ ),
668
+ settings.globalSearch && /* @__PURE__ */ jsx(
669
+ Button,
670
+ {
671
+ variant: "ghost",
672
+ size: "icon",
673
+ className: "absolute right-0 top-0 h-8 w-8",
674
+ "aria-label": "Clear table search",
675
+ onClick: () => handleSearch(""),
676
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
677
+ }
678
+ )
679
+ ] });
680
+ return /* @__PURE__ */ jsxs(
681
+ "div",
682
+ {
683
+ className: compact ? "eda-table-toolbar-compact" : "eda-table-toolbar",
684
+ children: [
685
+ !compact && search,
686
+ localFilters && /* @__PURE__ */ jsxs(Popover, { children: [
687
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
688
+ Button,
689
+ {
690
+ variant: "ghost",
691
+ size: "sm",
692
+ className: "eda-columns-trigger",
693
+ "aria-label": "Choose visible columns",
694
+ children: [
695
+ /* @__PURE__ */ jsx(Columns3, { className: "h-3.5 w-3.5" }),
696
+ " Columns",
697
+ " ",
698
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: settings.columns.length })
699
+ ]
700
+ }
701
+ ) }),
702
+ /* @__PURE__ */ jsxs(
703
+ PopoverContent,
704
+ {
705
+ align: "end",
706
+ "aria-label": "Visible columns",
707
+ className: "w-96 space-y-3",
708
+ children: [
709
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
710
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Visible columns" }),
711
+ /* @__PURE__ */ jsx(
712
+ Button,
713
+ {
714
+ variant: "ghost",
715
+ size: "sm",
716
+ onClick: () => onSettingsChange?.({
717
+ columns: getColumnNames().filter((field) => field !== "__ID").map(
718
+ (field) => settings.columns.find(
719
+ (column) => column.field === field
720
+ ) ?? { id: field, field }
721
+ )
722
+ }),
723
+ children: "Show all"
724
+ }
725
+ )
726
+ ] }),
727
+ /* @__PURE__ */ jsx(
728
+ Input,
729
+ {
730
+ "aria-label": "Find columns",
731
+ placeholder: "Find a column\u2026",
732
+ value: fieldSearch,
733
+ onChange: (event) => setFieldSearch(event.target.value)
734
+ }
735
+ ),
736
+ /* @__PURE__ */ jsx("div", { className: "grid max-h-72 gap-1 overflow-y-auto", children: getColumnNames().filter(
737
+ (field) => field !== "__ID" && field.toLowerCase().includes(fieldSearch.toLowerCase())
738
+ ).map((field) => {
739
+ const checked = settings.columns.some(
740
+ (column) => column.field === field
741
+ );
742
+ return /* @__PURE__ */ jsxs(
743
+ "label",
744
+ {
745
+ className: "flex min-w-0 cursor-pointer items-center gap-2 rounded px-2 py-2 hover:bg-muted",
746
+ children: [
747
+ /* @__PURE__ */ jsx(
748
+ "input",
749
+ {
750
+ type: "checkbox",
751
+ checked,
752
+ "aria-label": getFieldLabel(field),
753
+ onChange: (event) => onSettingsChange?.({
754
+ columns: event.target.checked ? [...settings.columns, { id: field, field }] : settings.columns.filter(
755
+ (column) => column.field !== field
756
+ )
757
+ })
758
+ }
759
+ ),
760
+ /* @__PURE__ */ jsx(
761
+ FieldMetadata,
762
+ {
763
+ profile: resolveFieldProfile(
764
+ field,
765
+ fieldProfiles,
766
+ getColumnData
767
+ ),
768
+ label: getFieldLabel(field),
769
+ compact: true
770
+ }
771
+ )
772
+ ]
773
+ },
774
+ field
775
+ );
776
+ }) })
777
+ ]
778
+ }
779
+ )
780
+ ] }),
781
+ /* @__PURE__ */ jsxs(
782
+ "span",
783
+ {
784
+ className: "ml-auto whitespace-nowrap text-muted-foreground tabular-nums",
785
+ "aria-live": "polite",
786
+ children: [
787
+ filteredData.length.toLocaleString(),
788
+ " ",
789
+ filteredData.length === 1 ? "row" : "rows"
790
+ ]
791
+ }
792
+ ),
793
+ compact && /* @__PURE__ */ jsxs(Popover, { children: [
794
+ /* @__PURE__ */ jsx(ActionTooltip, { content: "Search rows", children: /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
795
+ Button,
796
+ {
797
+ variant: "ghost",
798
+ size: "icon",
799
+ "aria-label": settings.globalSearch ? `Search rows: ${settings.globalSearch}` : "Search rows",
800
+ className: settings.globalSearch ? "text-primary bg-accent" : "",
801
+ children: /* @__PURE__ */ jsx(Search, { className: "h-3.5 w-3.5" })
802
+ }
803
+ ) }) }),
804
+ /* @__PURE__ */ jsx(
805
+ PopoverContent,
806
+ {
807
+ align: "end",
808
+ className: "eda-table-search-popover w-64 p-2",
809
+ children: search
810
+ }
811
+ )
812
+ ] }),
813
+ /* @__PURE__ */ jsx(ActionTooltip, { content: "Export rows as CSV", children: /* @__PURE__ */ jsx(
814
+ Button,
815
+ {
816
+ variant: "ghost",
817
+ size: "icon",
818
+ onClick: handleExport,
819
+ "aria-label": "Export rows as CSV",
820
+ children: /* @__PURE__ */ jsx(Download, { className: "h-3.5 w-3.5" })
821
+ }
822
+ ) })
823
+ ]
824
+ }
825
+ );
826
+ }
827
+ function toTableCsv(rows, fields) {
828
+ const cell = (value) => {
829
+ const text = String(value ?? "");
830
+ return /[",\r\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
831
+ };
832
+ return [
833
+ fields.map(cell).join(","),
834
+ ...rows.map((row) => fields.map((field) => cell(row[field])).join(","))
835
+ ].join("\n");
836
+ }
837
+ function DataTable({
838
+ settings,
839
+ height,
840
+ width,
841
+ rows,
842
+ onSettingsChange,
843
+ toolbarTarget
844
+ }) {
845
+ const data = useDataLayer((state) => state.data);
846
+ const liveItems = useDataLayer((state) => state.getLiveItems(settings));
847
+ const updateChart = useDataLayer((state) => state.updateChart);
848
+ const getColumnData = useDataLayer((state) => state.getColumnData);
849
+ const calculations = useDataLayer((state) => state.calculations);
850
+ const nonce = useDataLayer((state) => state.nonce);
851
+ const resolvedRows = useMemo(() => {
852
+ const source = rows ?? data;
853
+ if (!calculations.length) {
854
+ return source;
855
+ }
856
+ const columns = calculations.map(
857
+ (calc) => [calc.resultColumnName, getColumnData(calc.resultColumnName)]
858
+ );
859
+ return source.map((row) => ({
860
+ ...row,
861
+ ...Object.fromEntries(
862
+ columns.map(([name, values]) => [name, values[row.__ID]])
863
+ )
864
+ }));
865
+ }, [rows, data, calculations, getColumnData, nonce]);
866
+ const scrollRef = useRef(null);
867
+ const [scrollTop, setScrollTop] = useState(0);
868
+ const [columnWidths, setColumnWidths] = useState({});
869
+ const update = onSettingsChange ?? ((next) => updateChart(settings.id, next));
870
+ const filteredRows = useMemo(
871
+ () => getFilteredRows(
872
+ resolvedRows,
873
+ rows ? {
874
+ items: rows.map((row) => ({ key: row.__ID, value: 1 }))} : liveItems,
875
+ settings
876
+ ),
877
+ [resolvedRows, rows, liveItems, settings]
878
+ );
879
+ useEffect(() => {
880
+ if (scrollRef.current) {
881
+ scrollRef.current.scrollTop = 0;
882
+ }
883
+ setScrollTop(0);
884
+ }, [
885
+ settings.globalSearch,
886
+ settings.filters,
887
+ settings.sortBy,
888
+ settings.sortDirection,
889
+ filteredRows.length
890
+ ]);
891
+ const toolbar = /* @__PURE__ */ jsx(
892
+ DataTableToolbar,
893
+ {
894
+ settings,
895
+ rows: filteredRows,
896
+ onSettingsChange: update,
897
+ compact: toolbarTarget !== void 0,
898
+ localFilters: rows !== void 0
899
+ }
900
+ );
901
+ return /* @__PURE__ */ jsxs(
902
+ "div",
903
+ {
904
+ className: "eda-table-view flex min-h-0 flex-col w-full",
905
+ style: { height },
906
+ children: [
907
+ toolbarTarget === void 0 ? toolbar : toolbarTarget && createPortal(toolbar, toolbarTarget),
908
+ /* @__PURE__ */ jsx(
909
+ "div",
910
+ {
911
+ ref: scrollRef,
912
+ className: "eda-table-scroll relative min-h-0 flex-1 overflow-auto",
913
+ tabIndex: 0,
914
+ "aria-label": "Data rows, scroll to see more",
915
+ onScroll: (event) => setScrollTop(event.currentTarget.scrollTop),
916
+ children: /* @__PURE__ */ jsxs(
917
+ "table",
918
+ {
919
+ className: "eda-data-table border-collapse",
920
+ "aria-rowcount": filteredRows.length + 1,
921
+ style: {
922
+ tableLayout: "fixed",
923
+ width: Math.max(
924
+ width,
925
+ settings.columns.reduce(
926
+ (sum, column) => sum + (columnWidths[column.id] ?? column.width ?? Math.max(110, column.field.length * 7 + 42)),
927
+ 0
928
+ )
929
+ )
930
+ },
931
+ children: [
932
+ /* @__PURE__ */ jsx("caption", { className: "sr-only", children: getChartSummary(settings) }),
933
+ /* @__PURE__ */ jsx("colgroup", { children: settings.columns.map((column) => /* @__PURE__ */ jsx(
934
+ "col",
935
+ {
936
+ style: {
937
+ width: columnWidths[column.id] ?? column.width ?? Math.max(110, column.field.length * 7 + 42)
938
+ }
939
+ },
940
+ column.id
941
+ )) }),
942
+ /* @__PURE__ */ jsx(
943
+ DataTableHeader,
944
+ {
945
+ localFilters: rows !== void 0,
946
+ settings,
947
+ onSettingsChange: update,
948
+ onColumnResize: (id, width2) => setColumnWidths((current) => {
949
+ const next = { ...current };
950
+ if (width2 === null) {
951
+ delete next[id];
952
+ } else {
953
+ next[id] = width2;
954
+ }
955
+ return next;
956
+ })
957
+ }
958
+ ),
959
+ /* @__PURE__ */ jsx(
960
+ DataTableBody,
961
+ {
962
+ settings,
963
+ rows: filteredRows,
964
+ scrollTop,
965
+ viewportHeight: height - (toolbarTarget === void 0 ? 38 : 0)
966
+ }
967
+ )
968
+ ]
969
+ }
970
+ )
971
+ }
972
+ )
973
+ ]
974
+ }
975
+ );
976
+ }
977
+ function DataTableSettingsPanel({
978
+ settings,
979
+ onSettingsChange
980
+ }) {
981
+ const availableFields = useColumnNames();
982
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
983
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
984
+ /* @__PURE__ */ jsx(Label, { children: "Columns" }),
985
+ /* @__PURE__ */ jsx(
986
+ multi_select_default,
987
+ {
988
+ options: availableFields.map((f) => ({
989
+ label: f,
990
+ value: f
991
+ })),
992
+ value: settings.columns.map((col) => ({
993
+ label: col.field,
994
+ value: col.field
995
+ })),
996
+ onChange: (values) => onSettingsChange({
997
+ ...settings,
998
+ columns: values.map(
999
+ (v) => settings.columns.find(
1000
+ (column) => column.field === v.value
1001
+ ) ?? {
1002
+ id: v.value,
1003
+ field: v.value
1004
+ }
1005
+ )
1006
+ })
1007
+ }
1008
+ )
1009
+ ] }),
1010
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1011
+ /* @__PURE__ */ jsx(Label, { children: "Global Search" }),
1012
+ /* @__PURE__ */ jsx(
1013
+ Input,
1014
+ {
1015
+ placeholder: "Search all columns...",
1016
+ value: settings.globalSearch,
1017
+ onChange: (e) => onSettingsChange({
1018
+ ...settings,
1019
+ globalSearch: e.target.value
1020
+ })
1021
+ }
1022
+ )
1023
+ ] })
1024
+ ] });
1025
+ }
1026
+
1027
+ // src/components/charts/DataTable/definition.ts
1028
+ var dataTableDefinition = {
1029
+ type: "data-table",
1030
+ name: "Data Table",
1031
+ description: "Interactive data table with sorting and filtering",
1032
+ icon: Table,
1033
+ component: DataTable,
1034
+ settingsPanel: DataTableSettingsPanel,
1035
+ createDefaultSettings: (layout) => ({
1036
+ ...DEFAULT_CHART_SETTINGS,
1037
+ id: crypto.randomUUID(),
1038
+ type: "data-table",
1039
+ title: "Data Table",
1040
+ layout,
1041
+ margin: { top: 5, right: 5, bottom: 5, left: 5 },
1042
+ columns: [],
1043
+ sortDirection: "asc",
1044
+ filters: [],
1045
+ globalSearch: ""
1046
+ }),
1047
+ validateSettings: (settings) => {
1048
+ return settings.columns.length > 0;
1049
+ },
1050
+ getFilterFunction: (settings, fieldGetter) => {
1051
+ if (settings.filters.length === 0) {
1052
+ return () => true;
1053
+ }
1054
+ const filterFunctions = settings.filters.map((filter) => {
1055
+ const column = settings.columns.find((col) => col.field === filter.field);
1056
+ if (!column) {
1057
+ return () => true;
1058
+ }
1059
+ const dataHash = fieldGetter(column.field);
1060
+ return (d) => {
1061
+ const value = dataHash[d];
1062
+ return applyFilter(value, filter);
1063
+ };
1064
+ });
1065
+ return (d) => filterFunctions.every((fn) => fn(d));
1066
+ }
1067
+ };
1068
+
1069
+ export { DataTable, dataTableDefinition };
1070
+ //# sourceMappingURL=chunk-6U5DOIQM.js.map
1071
+ //# sourceMappingURL=chunk-6U5DOIQM.js.map