bolt-table 0.1.36 → 0.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -33,7 +33,8 @@ __export(index_exports, {
33
33
  BoltTable: () => BoltTable,
34
34
  DraggableHeader: () => DraggableHeader_default,
35
35
  ResizeOverlay: () => ResizeOverlay_default,
36
- TableBody: () => TableBody_default
36
+ TableBody: () => TableBody_default,
37
+ defineConfig: () => defineConfig
37
38
  });
38
39
  module.exports = __toCommonJS(index_exports);
39
40
 
@@ -136,6 +137,18 @@ var XIcon = ({ style, className }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx
136
137
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M18 6 6 18" }),
137
138
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m6 6 12 12" })
138
139
  ] });
140
+ var SparklesIcon = ({ style, className }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgBase, style, className, children: [
141
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z" }),
142
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5 3v4" }),
143
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M19 17v4" }),
144
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 5h4" }),
145
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M17 19h4" })
146
+ ] });
147
+ var SendIcon = ({ style, className }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgBase, style, className, children: [
148
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m22 2-7 20-4-9-9-4Z" }),
149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M22 2 11 13" })
150
+ ] });
151
+ var LoaderIcon = ({ style, className }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...svgBase, style, className, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
139
152
 
140
153
  // src/DraggableHeader.tsx
141
154
  var import_jsx_runtime2 = require("react/jsx-runtime");
@@ -677,6 +690,298 @@ var DraggableHeader = import_react.default.memo(
677
690
  DraggableHeader.displayName = "DraggableHeader";
678
691
  var DraggableHeader_default = DraggableHeader;
679
692
 
693
+ // src/ai.ts
694
+ function detectColumnType(key, data) {
695
+ const values = [];
696
+ for (let i = 0; i < Math.min(data.length, 20); i++) {
697
+ const v = data[i]?.[key];
698
+ if (v != null) values.push(v);
699
+ }
700
+ if (values.length === 0) return { type: "unknown", sample: "" };
701
+ const allNumbers = values.every((v) => typeof v === "number");
702
+ const allBooleans = values.every((v) => typeof v === "boolean");
703
+ const uniqueVals = [...new Set(values.map(String))];
704
+ const sampleStr = uniqueVals.length <= 8 ? uniqueVals.join(", ") : uniqueVals.slice(0, 6).join(", ") + "...";
705
+ if (allBooleans) return { type: "boolean", sample: sampleStr };
706
+ if (allNumbers) {
707
+ const nums = values;
708
+ const min = Math.min(...nums);
709
+ const max = Math.max(...nums);
710
+ return { type: "number", sample: `range ${min}\u2013${max}` };
711
+ }
712
+ return { type: "string", sample: sampleStr };
713
+ }
714
+ var cachedSchema = null;
715
+ function buildSchemaFingerprint(columns, dataLen) {
716
+ return columns.filter((c) => c.key !== "__select__" && c.key !== "__expand__").map((c) => c.key).join(",") + `:${dataLen}`;
717
+ }
718
+ function buildSystemPrompt(columns, data) {
719
+ const fingerprint = buildSchemaFingerprint(columns, data.length);
720
+ if (cachedSchema?.fingerprint === fingerprint) {
721
+ return cachedSchema.prompt;
722
+ }
723
+ const cols = columns.filter((c) => c.key !== "__select__" && c.key !== "__expand__");
724
+ const schema = cols.map((c) => {
725
+ const key = c.dataIndex ?? c.key;
726
+ const title = typeof c.title === "string" ? c.title : c.key;
727
+ const info = detectColumnType(key, data);
728
+ const w = c.width ?? 150;
729
+ const pin = c.pinned ? `, pinned: "${c.pinned}"` : "";
730
+ const hidden = c.hidden ? ", hidden: true" : "";
731
+ const vals = info.sample ? "|vals: " + info.sample : "";
732
+ return ` ${c.key}|${key}|"${title}"|${info.type}|w:${w}${pin}${hidden}${vals}`;
733
+ }).join("\n");
734
+ const sample = data.slice(0, 3).map((row) => {
735
+ const obj = {};
736
+ for (const col of cols) {
737
+ const di = col.dataIndex ?? col.key;
738
+ obj[di] = row[di];
739
+ }
740
+ return obj;
741
+ });
742
+ const prompt = `Data table AI. Respond ONLY with valid JSON, no markdown/explanation.
743
+
744
+ SCHEMA (key|dataIndex|title|type|width|flags|sample):
745
+ ${schema}
746
+
747
+ SAMPLE (${sample.length}/${data.length} rows):
748
+ ${JSON.stringify(sample)}
749
+
750
+ COLUMN ORDER: [${cols.map((c) => `"${c.key}"`).join(",")}]
751
+
752
+ OPS (combine any):
753
+ filter: {type:"filter",conditions:[{column:"<dataIndex>",op:"<op>",value:<v>}],logic:"and"|"or"}
754
+ sort: {type:"sort",column:"<dataIndex>",direction:"asc"|"desc"}
755
+ rowStyle: {type:"rowStyle",conditions:[...],logic:"and"|"or",style:{cssProp:"val"}}
756
+ cellStyle: {type:"cellStyle",column:"<dataIndex>",conditions:[...],logic:"and"|"or",style:{cssProp:"val"}}
757
+ hideColumns: {type:"hideColumns",columns:["key",...]}
758
+ showColumns: {type:"showColumns",columns:["key",...]}
759
+ resizeColumn: {type:"resizeColumn",column:"<key>",width:<px>}
760
+ reorderColumns: {type:"reorderColumns",order:["key1","key2",...]} (full column order)
761
+ pinColumn: {type:"pinColumn",column:"<key>",pinned:"left"|"right"|false}
762
+ setPage: {type:"setPage",page:<number>}
763
+
764
+ OPS: eq,neq,gt,gte,lt,lte,contains,notContains,startsWith,endsWith,in,notIn
765
+
766
+ FORMAT: {"operations":[...],"message":"brief description"}
767
+
768
+ RULES:
769
+ - Use dataIndex for data ops, key for column ops (hide/show/resize/reorder/pin).
770
+ - Colors: semi-transparent rgba. CSS props: camelCase.
771
+ - reorderColumns: provide FULL ordered array of ALL visible column keys.
772
+ - resizeColumn width: integer pixels (min 40, max 800).
773
+ - Combine multiple ops freely. Message: concise plain English.`;
774
+ cachedSchema = { fingerprint, prompt };
775
+ return prompt;
776
+ }
777
+ async function callAI(config, systemPrompt, userQuery) {
778
+ const { provider, apiKey, model, baseUrl, maxTokens = 1024, temperature = 0.1 } = config;
779
+ if (provider === "openai" || provider === "custom") {
780
+ const url = baseUrl ? `${baseUrl.replace(/\/$/, "")}/chat/completions` : "https://api.openai.com/v1/chat/completions";
781
+ const res = await fetch(url, {
782
+ method: "POST",
783
+ headers: {
784
+ "Content-Type": "application/json",
785
+ Authorization: `Bearer ${apiKey}`
786
+ },
787
+ body: JSON.stringify({
788
+ model: model ?? "gpt-4o-mini",
789
+ messages: [
790
+ { role: "system", content: systemPrompt },
791
+ { role: "user", content: userQuery }
792
+ ],
793
+ max_tokens: maxTokens,
794
+ temperature
795
+ })
796
+ });
797
+ if (!res.ok) {
798
+ const body = await res.text().catch(() => "");
799
+ throw new Error(`AI request failed (${res.status}): ${body}`);
800
+ }
801
+ const json = await res.json();
802
+ return json.choices?.[0]?.message?.content ?? "";
803
+ }
804
+ if (provider === "anthropic") {
805
+ const url = baseUrl ? `${baseUrl.replace(/\/$/, "")}/messages` : "https://api.anthropic.com/v1/messages";
806
+ const res = await fetch(url, {
807
+ method: "POST",
808
+ headers: {
809
+ "Content-Type": "application/json",
810
+ "x-api-key": apiKey,
811
+ "anthropic-version": "2023-06-01",
812
+ "anthropic-dangerous-direct-browser-access": "true"
813
+ },
814
+ body: JSON.stringify({
815
+ model: model ?? "claude-sonnet-4-20250514",
816
+ system: systemPrompt,
817
+ messages: [{ role: "user", content: userQuery }],
818
+ max_tokens: maxTokens,
819
+ temperature
820
+ })
821
+ });
822
+ if (!res.ok) {
823
+ const body = await res.text().catch(() => "");
824
+ throw new Error(`AI request failed (${res.status}): ${body}`);
825
+ }
826
+ const json = await res.json();
827
+ const textBlock = json.content?.find(
828
+ (b) => b.type === "text"
829
+ );
830
+ return textBlock?.text ?? "";
831
+ }
832
+ throw new Error(`Unsupported AI provider: ${provider}`);
833
+ }
834
+ function parseAIResponse(raw) {
835
+ let text = raw.trim();
836
+ const fenceMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
837
+ if (fenceMatch) text = fenceMatch[1].trim();
838
+ const start = text.indexOf("{");
839
+ const end = text.lastIndexOf("}");
840
+ if (start !== -1 && end > start) {
841
+ text = text.slice(start, end + 1);
842
+ }
843
+ const parsed = JSON.parse(text);
844
+ if (!parsed.operations || !Array.isArray(parsed.operations)) {
845
+ throw new Error("Invalid AI response: missing operations array");
846
+ }
847
+ return {
848
+ operations: parsed.operations,
849
+ message: parsed.message ?? "AI operations applied."
850
+ };
851
+ }
852
+ function evaluateCondition(condition, row) {
853
+ const rawVal = row[condition.column];
854
+ const target = condition.value;
855
+ switch (condition.op) {
856
+ case "eq":
857
+ return rawVal == target;
858
+ case "neq":
859
+ return rawVal != target;
860
+ case "gt":
861
+ return Number(rawVal) > Number(target);
862
+ case "gte":
863
+ return Number(rawVal) >= Number(target);
864
+ case "lt":
865
+ return Number(rawVal) < Number(target);
866
+ case "lte":
867
+ return Number(rawVal) <= Number(target);
868
+ case "contains":
869
+ return String(rawVal ?? "").toLowerCase().includes(String(target).toLowerCase());
870
+ case "notContains":
871
+ return !String(rawVal ?? "").toLowerCase().includes(String(target).toLowerCase());
872
+ case "startsWith":
873
+ return String(rawVal ?? "").toLowerCase().startsWith(String(target).toLowerCase());
874
+ case "endsWith":
875
+ return String(rawVal ?? "").toLowerCase().endsWith(String(target).toLowerCase());
876
+ case "in":
877
+ if (Array.isArray(target)) {
878
+ return target.some((t) => rawVal == t);
879
+ }
880
+ return false;
881
+ case "notIn":
882
+ if (Array.isArray(target)) {
883
+ return !target.some((t) => rawVal == t);
884
+ }
885
+ return true;
886
+ default:
887
+ return true;
888
+ }
889
+ }
890
+ function matchesConditions(conditions, logic, row) {
891
+ if (!conditions || conditions.length === 0) return true;
892
+ if (logic === "or") {
893
+ return conditions.some((c) => evaluateCondition(c, row));
894
+ }
895
+ return conditions.every((c) => evaluateCondition(c, row));
896
+ }
897
+ function applyAIFilter(data, op) {
898
+ return data.filter((row) => matchesConditions(op.conditions, op.logic, row));
899
+ }
900
+ function applyAISort(data, op) {
901
+ const dir = op.direction === "asc" ? 1 : -1;
902
+ const col = op.column;
903
+ return [...data].sort((a, b) => {
904
+ const aVal = a[col];
905
+ const bVal = b[col];
906
+ if (aVal == null && bVal == null) return 0;
907
+ if (aVal == null) return 1;
908
+ if (bVal == null) return -1;
909
+ if (typeof aVal === "number" && typeof bVal === "number")
910
+ return (aVal - bVal) * dir;
911
+ return String(aVal).localeCompare(String(bVal)) * dir;
912
+ });
913
+ }
914
+ function getAIRowStyle(row, ops) {
915
+ let merged;
916
+ for (const op of ops) {
917
+ if (matchesConditions(op.conditions, op.logic, row)) {
918
+ if (!merged) merged = {};
919
+ Object.assign(merged, op.style);
920
+ }
921
+ }
922
+ return merged;
923
+ }
924
+ function getAICellStyle(row, columnKey, ops) {
925
+ let merged;
926
+ for (const op of ops) {
927
+ if (op.column === columnKey && matchesConditions(op.conditions, op.logic, row)) {
928
+ if (!merged) merged = {};
929
+ Object.assign(merged, op.style);
930
+ }
931
+ }
932
+ return merged;
933
+ }
934
+ function applyAIOperations(data, operations) {
935
+ let filteredData = data;
936
+ let sortOp = null;
937
+ const styleOps = [];
938
+ const cellStyleOps = [];
939
+ const hideColumns = [];
940
+ const showColumns = [];
941
+ const resizeOps = [];
942
+ let reorderOp = null;
943
+ const pinOps = [];
944
+ let setPageOp = null;
945
+ for (const op of operations) {
946
+ switch (op.type) {
947
+ case "filter":
948
+ filteredData = applyAIFilter(filteredData, op);
949
+ break;
950
+ case "sort":
951
+ sortOp = op;
952
+ break;
953
+ case "rowStyle":
954
+ styleOps.push(op);
955
+ break;
956
+ case "cellStyle":
957
+ cellStyleOps.push(op);
958
+ break;
959
+ case "hideColumns":
960
+ hideColumns.push(...op.columns);
961
+ break;
962
+ case "showColumns":
963
+ showColumns.push(...op.columns);
964
+ break;
965
+ case "resizeColumn":
966
+ resizeOps.push(op);
967
+ break;
968
+ case "reorderColumns":
969
+ reorderOp = op;
970
+ break;
971
+ case "pinColumn":
972
+ pinOps.push(op);
973
+ break;
974
+ case "setPage":
975
+ setPageOp = op;
976
+ break;
977
+ }
978
+ }
979
+ if (sortOp) {
980
+ filteredData = applyAISort(filteredData, sortOp);
981
+ }
982
+ return { filteredData, sortOp, styleOps, cellStyleOps, hideColumns, showColumns, resizeOps, reorderOp, pinOps, setPageOp };
983
+ }
984
+
680
985
  // src/ResizeOverlay.tsx
681
986
  var import_react2 = require("react");
682
987
  var import_jsx_runtime3 = require("react/jsx-runtime");
@@ -898,9 +1203,11 @@ var Cell = import_react3.default.memo(
898
1203
  isLoading,
899
1204
  onEdit,
900
1205
  isEditing,
901
- onEditComplete
1206
+ onEditComplete,
1207
+ cellStyleFn
902
1208
  }) => {
903
1209
  const isPinned = Boolean(column.pinned);
1210
+ const extraCellStyle = cellStyleFn?.(record, column.dataIndex ?? column.key);
904
1211
  if (isLoading && column.key !== "__select__" && column.key !== "__expand__") {
905
1212
  const shimmerContent = column.shimmerRender ? column.shimmerRender() : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
906
1213
  "div",
@@ -1052,7 +1359,8 @@ var Cell = import_react3.default.memo(
1052
1359
  minWidth: 0,
1053
1360
  ...column.style,
1054
1361
  ...isPinned ? styles?.pinnedCell : void 0,
1055
- ...styles?.cell
1362
+ ...styles?.cell,
1363
+ ...extraCellStyle
1056
1364
  },
1057
1365
  children: isSystem ? content : showEditor ? content : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1058
1366
  "div",
@@ -1079,6 +1387,7 @@ var Cell = import_react3.default.memo(
1079
1387
  if (prev.onEdit !== next.onEdit) return false;
1080
1388
  if (prev.isEditing !== next.isEditing) return false;
1081
1389
  if (prev.onEditComplete !== next.onEditComplete) return false;
1390
+ if (prev.cellStyleFn !== next.cellStyleFn) return false;
1082
1391
  if (prev.column.key === "__select__") {
1083
1392
  return prev.isSelected === next.isSelected && prev.normalizedSelectedKeys === next.normalizedSelectedKeys;
1084
1393
  }
@@ -1180,7 +1489,8 @@ var TableBody = ({
1180
1489
  onEditComplete,
1181
1490
  enableDynamicRowHeight = false,
1182
1491
  onRowHeightChange,
1183
- columnGridIndexMap
1492
+ columnGridIndexMap,
1493
+ cellStyleFn
1184
1494
  }) => {
1185
1495
  const virtualItems = rowVirtualizer.getVirtualItems();
1186
1496
  const totalSize = rowVirtualizer.getTotalSize();
@@ -1309,7 +1619,8 @@ var TableBody = ({
1309
1619
  recordFingerprint,
1310
1620
  onEdit,
1311
1621
  isEditing: editingCell?.rowKey === rowKey && editingCell?.columnKey === col.key,
1312
- onEditComplete
1622
+ onEditComplete,
1623
+ cellStyleFn
1313
1624
  }
1314
1625
  )
1315
1626
  }
@@ -1346,7 +1657,8 @@ var TableBody = ({
1346
1657
  recordFingerprint,
1347
1658
  onEdit,
1348
1659
  isEditing: editingCell?.rowKey === rowKey && editingCell?.columnKey === col.key,
1349
- onEditComplete
1660
+ onEditComplete,
1661
+ cellStyleFn
1350
1662
  }
1351
1663
  )
1352
1664
  }
@@ -1536,7 +1848,8 @@ var TableBody = ({
1536
1848
  recordFingerprint,
1537
1849
  onEdit,
1538
1850
  isEditing: editingCell?.rowKey === rk && editingCell?.columnKey === col.key,
1539
- onEditComplete
1851
+ onEditComplete,
1852
+ cellStyleFn
1540
1853
  }
1541
1854
  )
1542
1855
  }
@@ -1664,7 +1977,8 @@ var TableBody = ({
1664
1977
  recordFingerprint,
1665
1978
  onEdit,
1666
1979
  isEditing: editingCell?.rowKey === rk && editingCell?.columnKey === col.key,
1667
- onEditComplete
1980
+ onEditComplete,
1981
+ cellStyleFn
1668
1982
  }
1669
1983
  )
1670
1984
  }
@@ -1759,7 +2073,13 @@ function BoltTable({
1759
2073
  globalSearchValue,
1760
2074
  onGlobalSearchChange,
1761
2075
  toolbarContent,
1762
- columnSettingsLabel
2076
+ columnSettingsLabel,
2077
+ aiMode = false,
2078
+ aiConfig,
2079
+ onAIQuery,
2080
+ onAIResponse,
2081
+ aiPlaceholder = "Ask AI anything about your data...",
2082
+ aiButtonLabel
1763
2083
  }) {
1764
2084
  const data = (0, import_react4.useMemo)(() => {
1765
2085
  if (!Array.isArray(rawData)) return STABLE_EMPTY_DATA;
@@ -1933,6 +2253,213 @@ function BoltTable({
1933
2253
  document.removeEventListener("keydown", onKey);
1934
2254
  };
1935
2255
  }, [showColumnPicker]);
2256
+ const [aiBarOpen, setAiBarOpen] = (0, import_react4.useState)(false);
2257
+ const [aiQuery, setAiQuery] = (0, import_react4.useState)("");
2258
+ const [aiLoading, setAiLoading] = (0, import_react4.useState)(false);
2259
+ const [aiResult, setAiResult] = (0, import_react4.useState)(null);
2260
+ const [aiError, setAiError] = (0, import_react4.useState)(null);
2261
+ const aiInputRef = (0, import_react4.useRef)(null);
2262
+ const [aiStyleOps, setAiStyleOps] = (0, import_react4.useState)([]);
2263
+ const [aiCellStyleOps, setAiCellStyleOps] = (0, import_react4.useState)(
2264
+ []
2265
+ );
2266
+ const [aiFilteredDataKeys, setAiFilteredDataKeys] = (0, import_react4.useState)(null);
2267
+ const [aiSortKey, setAiSortKey] = (0, import_react4.useState)(null);
2268
+ const [aiSortDir, setAiSortDir] = (0, import_react4.useState)(null);
2269
+ const aiFiltersStorageKey = columnPersistence && typeof columnPersistence === "object" ? `bt-ai-filters-${columnPersistence.storageKey}` : "bt-ai-filters";
2270
+ const [savedAIFilters, setSavedAIFilters] = (0, import_react4.useState)(() => {
2271
+ try {
2272
+ const raw = localStorage.getItem(aiFiltersStorageKey);
2273
+ return raw ? JSON.parse(raw) : [];
2274
+ } catch {
2275
+ return [];
2276
+ }
2277
+ });
2278
+ const [showSavedFilters, setShowSavedFilters] = (0, import_react4.useState)(false);
2279
+ const savedFiltersRef = (0, import_react4.useRef)(null);
2280
+ import_react4.default.useEffect(() => {
2281
+ if (!showSavedFilters) return;
2282
+ const close = (e) => {
2283
+ if (savedFiltersRef.current && !savedFiltersRef.current.contains(e.target)) {
2284
+ setShowSavedFilters(false);
2285
+ }
2286
+ };
2287
+ document.addEventListener("mousedown", close);
2288
+ return () => document.removeEventListener("mousedown", close);
2289
+ }, [showSavedFilters]);
2290
+ const saveCurrentAIFilter = (0, import_react4.useCallback)(() => {
2291
+ if (!aiResult) return;
2292
+ const label = aiQuery || aiResult.message;
2293
+ const entry = { label, operations: aiResult.operations, query: aiQuery };
2294
+ const next = [...savedAIFilters, entry];
2295
+ setSavedAIFilters(next);
2296
+ try {
2297
+ localStorage.setItem(aiFiltersStorageKey, JSON.stringify(next));
2298
+ } catch {
2299
+ }
2300
+ }, [aiResult, aiQuery, savedAIFilters, aiFiltersStorageKey]);
2301
+ const removeSavedFilter = (0, import_react4.useCallback)((index) => {
2302
+ const next = savedAIFilters.filter((_, i) => i !== index);
2303
+ setSavedAIFilters(next);
2304
+ try {
2305
+ localStorage.setItem(aiFiltersStorageKey, JSON.stringify(next));
2306
+ } catch {
2307
+ }
2308
+ }, [savedAIFilters, aiFiltersStorageKey]);
2309
+ const applySavedFilter = (0, import_react4.useCallback)((filter) => {
2310
+ const { filteredData, sortOp, styleOps: sOps, cellStyleOps: csOps, hideColumns: hideCols, showColumns: showCols, resizeOps, reorderOp, pinOps, setPageOp } = applyAIOperations(data, filter.operations);
2311
+ setAiStyleOps(sOps);
2312
+ setAiCellStyleOps(csOps);
2313
+ if (filter.operations.some((op) => op.type === "filter")) {
2314
+ const keySet = /* @__PURE__ */ new Set();
2315
+ filteredData.forEach((row, idx) => {
2316
+ const k = typeof rowKey === "function" ? rowKey(row) : String(row[typeof rowKey === "string" ? rowKey : "id"] ?? idx);
2317
+ keySet.add(k);
2318
+ });
2319
+ setAiFilteredDataKeys(keySet);
2320
+ } else {
2321
+ setAiFilteredDataKeys(null);
2322
+ }
2323
+ if (sortOp) {
2324
+ setAiSortKey(sortOp.column);
2325
+ setAiSortDir(sortOp.direction);
2326
+ } else {
2327
+ setAiSortKey(null);
2328
+ setAiSortDir(null);
2329
+ }
2330
+ if (hideCols.length > 0 || showCols.length > 0) {
2331
+ setColumns(
2332
+ (prev) => prev.map((col) => {
2333
+ if (hideCols.includes(col.key)) return { ...col, hidden: true };
2334
+ if (showCols.includes(col.key)) return { ...col, hidden: false };
2335
+ return col;
2336
+ })
2337
+ );
2338
+ }
2339
+ for (const rOp of resizeOps) {
2340
+ const w = Math.max(40, Math.min(800, rOp.width));
2341
+ setColumnWidths((prev) => {
2342
+ const n = new Map(prev);
2343
+ n.set(rOp.column, w);
2344
+ return n;
2345
+ });
2346
+ onColumnResize?.(rOp.column, w);
2347
+ }
2348
+ if (reorderOp) {
2349
+ setColumnOrder(reorderOp.order);
2350
+ onColumnOrderChange?.(reorderOp.order);
2351
+ }
2352
+ for (const pOp of pinOps) {
2353
+ setColumns(
2354
+ (prev) => prev.map((col) => col.key === pOp.column ? { ...col, pinned: pOp.pinned } : col)
2355
+ );
2356
+ onColumnPin?.(pOp.column, pOp.pinned);
2357
+ }
2358
+ if (setPageOp) {
2359
+ setInternalPage(setPageOp.page);
2360
+ }
2361
+ setAiResult({ operations: filter.operations, message: `Applied saved filter: ${filter.label}` });
2362
+ setShowSavedFilters(false);
2363
+ }, [data, rowKey, onColumnResize, onColumnOrderChange, onColumnPin]);
2364
+ const onAIResponseRef = (0, import_react4.useRef)(onAIResponse);
2365
+ onAIResponseRef.current = onAIResponse;
2366
+ const handleAISubmit = (0, import_react4.useCallback)(async () => {
2367
+ const query = aiQuery.trim();
2368
+ if (!query) return;
2369
+ setAiLoading(true);
2370
+ setAiError(null);
2371
+ try {
2372
+ let response;
2373
+ if (onAIQuery) {
2374
+ response = await onAIQuery(query, {
2375
+ data,
2376
+ columns: initialColumns
2377
+ });
2378
+ } else if (aiConfig) {
2379
+ const sysPrompt = buildSystemPrompt(initialColumns, data);
2380
+ const raw = await callAI(aiConfig, sysPrompt, query);
2381
+ response = parseAIResponse(raw);
2382
+ } else {
2383
+ throw new Error("AI mode requires either aiConfig or onAIQuery prop");
2384
+ }
2385
+ const { filteredData, sortOp, styleOps: sOps, cellStyleOps: csOps, hideColumns: hideCols, showColumns: showCols, resizeOps, reorderOp, pinOps, setPageOp } = applyAIOperations(data, response.operations);
2386
+ setAiStyleOps(sOps);
2387
+ setAiCellStyleOps(csOps);
2388
+ if (response.operations.some((op) => op.type === "filter")) {
2389
+ const keySet = /* @__PURE__ */ new Set();
2390
+ filteredData.forEach((row, idx) => {
2391
+ const k = typeof rowKey === "function" ? rowKey(row) : String(row[typeof rowKey === "string" ? rowKey : "id"] ?? idx);
2392
+ keySet.add(k);
2393
+ });
2394
+ setAiFilteredDataKeys(keySet);
2395
+ } else {
2396
+ setAiFilteredDataKeys(null);
2397
+ }
2398
+ if (sortOp) {
2399
+ setAiSortKey(sortOp.column);
2400
+ setAiSortDir(sortOp.direction);
2401
+ } else {
2402
+ setAiSortKey(null);
2403
+ setAiSortDir(null);
2404
+ }
2405
+ if (hideCols.length > 0 || showCols.length > 0) {
2406
+ setColumns(
2407
+ (prev) => prev.map((col) => {
2408
+ if (hideCols.includes(col.key)) return { ...col, hidden: true };
2409
+ if (showCols.includes(col.key)) return { ...col, hidden: false };
2410
+ return col;
2411
+ })
2412
+ );
2413
+ }
2414
+ for (const rOp of resizeOps) {
2415
+ const w = Math.max(40, Math.min(800, rOp.width));
2416
+ setColumnWidths((prev) => {
2417
+ const n = new Map(prev);
2418
+ n.set(rOp.column, w);
2419
+ return n;
2420
+ });
2421
+ onColumnResize?.(rOp.column, w);
2422
+ }
2423
+ if (reorderOp) {
2424
+ setColumnOrder(reorderOp.order);
2425
+ onColumnOrderChange?.(reorderOp.order);
2426
+ }
2427
+ for (const pOp of pinOps) {
2428
+ setColumns(
2429
+ (prev) => prev.map((col) => col.key === pOp.column ? { ...col, pinned: pOp.pinned } : col)
2430
+ );
2431
+ onColumnPin?.(pOp.column, pOp.pinned);
2432
+ }
2433
+ if (setPageOp) {
2434
+ setInternalPage(setPageOp.page);
2435
+ }
2436
+ setAiResult(response);
2437
+ onAIResponseRef.current?.(response);
2438
+ } catch (err) {
2439
+ setAiError(err instanceof Error ? err.message : "AI query failed");
2440
+ } finally {
2441
+ setAiLoading(false);
2442
+ }
2443
+ }, [aiQuery, aiConfig, onAIQuery, data, initialColumns, rowKey]);
2444
+ const handleAIClear = (0, import_react4.useCallback)(() => {
2445
+ setAiResult(null);
2446
+ setAiError(null);
2447
+ setAiStyleOps([]);
2448
+ setAiCellStyleOps([]);
2449
+ setAiFilteredDataKeys(null);
2450
+ setAiSortKey(null);
2451
+ setAiSortDir(null);
2452
+ setAiQuery("");
2453
+ }, []);
2454
+ const handleAIBarClose = (0, import_react4.useCallback)(() => {
2455
+ setAiBarOpen(false);
2456
+ handleAIClear();
2457
+ }, [handleAIClear]);
2458
+ import_react4.default.useEffect(() => {
2459
+ if (aiBarOpen && aiInputRef.current) {
2460
+ setTimeout(() => aiInputRef.current?.focus(), 300);
2461
+ }
2462
+ }, [aiBarOpen]);
1936
2463
  const columnsWithPersistedWidths = (0, import_react4.useMemo)(
1937
2464
  () => columns.map((col) => ({
1938
2465
  ...col,
@@ -2574,6 +3101,47 @@ function BoltTable({
2574
3101
  }
2575
3102
  return result;
2576
3103
  }, [data, sortState, columnFilters, globalSearchValue, internalGlobalSearch]);
3104
+ const aiProcessedData = (0, import_react4.useMemo)(() => {
3105
+ let result = processedData;
3106
+ if (aiFilteredDataKeys) {
3107
+ result = result.filter((row, idx) => {
3108
+ if (row == null) return false;
3109
+ const k = typeof rowKey === "function" ? rowKey(row) : String(
3110
+ row[typeof rowKey === "string" ? rowKey : "id"] ?? idx
3111
+ );
3112
+ return aiFilteredDataKeys.has(k);
3113
+ });
3114
+ }
3115
+ if (aiSortKey && aiSortDir) {
3116
+ const dir = aiSortDir === "asc" ? 1 : -1;
3117
+ const col = aiSortKey;
3118
+ result = [...result].sort((a, b) => {
3119
+ const aVal = a[col];
3120
+ const bVal = b[col];
3121
+ if (aVal == null && bVal == null) return 0;
3122
+ if (aVal == null) return 1;
3123
+ if (bVal == null) return -1;
3124
+ if (typeof aVal === "number" && typeof bVal === "number")
3125
+ return (aVal - bVal) * dir;
3126
+ return String(aVal).localeCompare(String(bVal)) * dir;
3127
+ });
3128
+ }
3129
+ return result;
3130
+ }, [processedData, aiFilteredDataKeys, aiSortKey, aiSortDir, rowKey]);
3131
+ const getAIRowStyleForRecord = (0, import_react4.useCallback)(
3132
+ (record) => {
3133
+ if (aiStyleOps.length === 0) return void 0;
3134
+ return getAIRowStyle(record, aiStyleOps);
3135
+ },
3136
+ [aiStyleOps]
3137
+ );
3138
+ const getAICellStyleForRecord = (0, import_react4.useCallback)(
3139
+ (record, columnKey) => {
3140
+ if (aiCellStyleOps.length === 0) return void 0;
3141
+ return getAICellStyle(record, columnKey, aiCellStyleOps);
3142
+ },
3143
+ [aiCellStyleOps]
3144
+ );
2577
3145
  const pinnedRowCacheRef = (0, import_react4.useRef)(/* @__PURE__ */ new Map());
2578
3146
  const { pinnedTopRows, pinnedBottomRows, unpinnedProcessedData } = (0, import_react4.useMemo)(() => {
2579
3147
  if (!resolvedRowPinning || !resolvedRowPinning.top?.length && !resolvedRowPinning.bottom?.length) {
@@ -2581,7 +3149,7 @@ function BoltTable({
2581
3149
  return {
2582
3150
  pinnedTopRows: [],
2583
3151
  pinnedBottomRows: [],
2584
- unpinnedProcessedData: processedData
3152
+ unpinnedProcessedData: aiProcessedData
2585
3153
  };
2586
3154
  }
2587
3155
  const topKeySet = new Set((resolvedRowPinning.top ?? []).map(String));
@@ -2591,7 +3159,7 @@ function BoltTable({
2591
3159
  const topMap = /* @__PURE__ */ new Map();
2592
3160
  const bottomMap = /* @__PURE__ */ new Map();
2593
3161
  const rest = [];
2594
- processedData.forEach((row, idx) => {
3162
+ aiProcessedData.forEach((row, idx) => {
2595
3163
  if (row == null) return;
2596
3164
  const key = getSafeRowKey(row, idx);
2597
3165
  if (topKeySet.has(key)) {
@@ -2631,7 +3199,7 @@ function BoltTable({
2631
3199
  unpinnedProcessedData: rest
2632
3200
  };
2633
3201
  }, [
2634
- processedData,
3202
+ aiProcessedData,
2635
3203
  resolvedRowPinning,
2636
3204
  getSafeRowKey,
2637
3205
  keepPinnedRowsAcrossPages
@@ -2700,7 +3268,7 @@ function BoltTable({
2700
3268
  return unpinnedProcessedData.slice(start, start + pgSize);
2701
3269
  }, [unpinnedProcessedData, needsClientPagination, pgCurrent, pgSize]);
2702
3270
  const shimmerCount = pgEnabled ? pgSize : 15;
2703
- const showShimmer = isLoading && processedData.length === 0;
3271
+ const showShimmer = isLoading && aiProcessedData.length === 0;
2704
3272
  const shimmerRowKeyField = typeof rowKey === "string" ? rowKey : "id";
2705
3273
  const shimmerData = (0, import_react4.useMemo)(() => {
2706
3274
  if (!showShimmer) return null;
@@ -2984,49 +3552,395 @@ function BoltTable({
2984
3552
  border: 1px dashed ${accentColor} !important;
2985
3553
  }
2986
3554
  ${onRowClick ? "[data-bt-cell] { cursor: pointer; }" : ""}
3555
+ @keyframes bt-spin { to { transform: rotate(360deg); } }
3556
+ @keyframes bt-ai-shimmer {
3557
+ 0% { background-position: -200% 0; }
3558
+ 100% { background-position: 200% 0; }
3559
+ }
2987
3560
  ` }),
2988
- (!hideGlobalSearch || showColumnSettings) && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3561
+ (!hideGlobalSearch || showColumnSettings || aiMode) && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2989
3562
  "div",
2990
3563
  {
2991
3564
  style: {
3565
+ position: "relative",
2992
3566
  display: "flex",
2993
3567
  alignItems: "center",
2994
3568
  gap: 8,
2995
3569
  padding: "6px 8px",
2996
3570
  borderBottom: "1px solid rgba(128,128,128,0.2)",
2997
3571
  fontSize: 12,
2998
- flexShrink: 0
3572
+ flexShrink: 0,
3573
+ zIndex: 20
2999
3574
  },
3000
3575
  children: [
3001
- !hideGlobalSearch && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3576
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3002
3577
  "div",
3003
3578
  {
3004
3579
  style: {
3005
3580
  display: "flex",
3006
3581
  alignItems: "center",
3007
- gap: 4,
3582
+ gap: 8,
3008
3583
  flex: "1 1 0%",
3009
- position: "relative"
3584
+ opacity: aiBarOpen ? 0 : 1,
3585
+ transform: aiBarOpen ? "scale(0.97)" : "scale(1)",
3586
+ transition: "opacity 0.25s ease, transform 0.25s ease",
3587
+ pointerEvents: aiBarOpen ? "none" : "auto",
3588
+ minWidth: 0
3589
+ },
3590
+ children: [
3591
+ !hideGlobalSearch && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3592
+ "div",
3593
+ {
3594
+ style: {
3595
+ display: "flex",
3596
+ alignItems: "center",
3597
+ gap: 4,
3598
+ flex: "1 1 0%",
3599
+ position: "relative"
3600
+ },
3601
+ children: [
3602
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3603
+ "span",
3604
+ {
3605
+ style: {
3606
+ display: "flex",
3607
+ color: "GrayText",
3608
+ flexShrink: 0
3609
+ },
3610
+ children: icons?.search ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SearchIcon, { style: { width: 14, height: 14 } })
3611
+ }
3612
+ ),
3613
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3614
+ "input",
3615
+ {
3616
+ type: "text",
3617
+ placeholder: "Search all columns...",
3618
+ value: globalSearchValue ?? internalGlobalSearch,
3619
+ onChange: (e) => {
3620
+ const v = e.target.value;
3621
+ if (onGlobalSearchChange) onGlobalSearchChange(v);
3622
+ else setInternalGlobalSearch(v);
3623
+ },
3624
+ style: {
3625
+ flex: "1 1 0%",
3626
+ border: "none",
3627
+ outline: "none",
3628
+ background: "transparent",
3629
+ font: "inherit",
3630
+ color: "inherit",
3631
+ padding: "4px 6px",
3632
+ minWidth: 0
3633
+ }
3634
+ }
3635
+ ),
3636
+ (globalSearchValue ?? internalGlobalSearch) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3637
+ "button",
3638
+ {
3639
+ type: "button",
3640
+ onClick: () => {
3641
+ if (onGlobalSearchChange) onGlobalSearchChange("");
3642
+ else setInternalGlobalSearch("");
3643
+ },
3644
+ style: {
3645
+ display: "flex",
3646
+ alignItems: "center",
3647
+ justifyContent: "center",
3648
+ background: "none",
3649
+ border: "none",
3650
+ cursor: "pointer",
3651
+ padding: 2,
3652
+ color: "GrayText",
3653
+ flexShrink: 0
3654
+ },
3655
+ children: icons?.close ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(XIcon, { style: { width: 12, height: 12 } })
3656
+ }
3657
+ )
3658
+ ]
3659
+ }
3660
+ ),
3661
+ toolbarContent,
3662
+ showColumnSettings && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { position: "relative", flexShrink: 0 }, children: [
3663
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3664
+ "button",
3665
+ {
3666
+ type: "button",
3667
+ onClick: () => setShowColumnPicker((p) => !p),
3668
+ style: {
3669
+ display: "flex",
3670
+ alignItems: "center",
3671
+ justifyContent: "center",
3672
+ background: "none",
3673
+ border: "1px solid rgba(128,128,128,0.2)",
3674
+ borderRadius: 4,
3675
+ cursor: "pointer",
3676
+ padding: "4px 6px",
3677
+ color: "inherit",
3678
+ gap: 4,
3679
+ fontSize: 12
3680
+ },
3681
+ title: "Column settings",
3682
+ children: [
3683
+ icons?.columns ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ColumnsIcon, { style: { width: 14, height: 14 } }),
3684
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: columnSettingsLabel ?? "Columns" })
3685
+ ]
3686
+ }
3687
+ ),
3688
+ showColumnPicker && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3689
+ "div",
3690
+ {
3691
+ ref: columnPickerRef,
3692
+ style: {
3693
+ position: "absolute",
3694
+ top: "100%",
3695
+ right: 0,
3696
+ zIndex: 99999,
3697
+ minWidth: 200,
3698
+ maxHeight: 320,
3699
+ overflowY: "auto",
3700
+ borderRadius: 8,
3701
+ border: "1px solid rgba(128,128,128,0.2)",
3702
+ boxShadow: "0 4px 24px rgba(0,0,0,0.12)",
3703
+ backdropFilter: "blur(16px)",
3704
+ WebkitBackdropFilter: "blur(16px)",
3705
+ backgroundColor: "rgba(128,128,128,0.08)",
3706
+ padding: "4px 0",
3707
+ marginTop: 4
3708
+ },
3709
+ children: initialColumns.filter(
3710
+ (c) => c.key !== "__select__" && c.key !== "__expand__"
3711
+ ).map((col) => {
3712
+ const current = columns.find(
3713
+ (c) => c.key === col.key
3714
+ );
3715
+ const isHidden = current?.hidden ?? false;
3716
+ const isPinned = !!current?.pinned;
3717
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3718
+ "label",
3719
+ {
3720
+ style: {
3721
+ display: "flex",
3722
+ alignItems: "center",
3723
+ gap: 8,
3724
+ padding: "6px 12px",
3725
+ cursor: isPinned ? "not-allowed" : "pointer",
3726
+ opacity: isPinned ? 0.5 : 1,
3727
+ fontSize: 12
3728
+ },
3729
+ children: [
3730
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3731
+ "input",
3732
+ {
3733
+ type: "checkbox",
3734
+ checked: !isHidden,
3735
+ disabled: isPinned,
3736
+ onChange: () => {
3737
+ if (isPinned) return;
3738
+ handleToggleHide(col.key);
3739
+ },
3740
+ style: {
3741
+ cursor: isPinned ? "not-allowed" : "pointer",
3742
+ accentColor
3743
+ }
3744
+ }
3745
+ ),
3746
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3747
+ "span",
3748
+ {
3749
+ style: {
3750
+ overflow: "hidden",
3751
+ textOverflow: "ellipsis",
3752
+ whiteSpace: "nowrap"
3753
+ },
3754
+ children: typeof col.title === "string" ? col.title : col.key
3755
+ }
3756
+ )
3757
+ ]
3758
+ },
3759
+ col.key
3760
+ );
3761
+ })
3762
+ }
3763
+ )
3764
+ ] }),
3765
+ aiMode && savedAIFilters.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: savedFiltersRef, style: { position: "relative", flexShrink: 0 }, children: [
3766
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3767
+ "button",
3768
+ {
3769
+ type: "button",
3770
+ onClick: () => setShowSavedFilters((p) => !p),
3771
+ style: {
3772
+ display: "flex",
3773
+ alignItems: "center",
3774
+ gap: 4,
3775
+ background: "none",
3776
+ border: "1px solid rgba(128,128,128,0.2)",
3777
+ borderRadius: 4,
3778
+ cursor: "pointer",
3779
+ padding: "4px 6px",
3780
+ color: "inherit",
3781
+ fontSize: 12
3782
+ },
3783
+ title: "Saved AI filters",
3784
+ children: [
3785
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polygon", { points: "22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" }) }),
3786
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: savedAIFilters.length })
3787
+ ]
3788
+ }
3789
+ ),
3790
+ showSavedFilters && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3791
+ "div",
3792
+ {
3793
+ style: {
3794
+ position: "absolute",
3795
+ top: "100%",
3796
+ right: 0,
3797
+ zIndex: 99999,
3798
+ minWidth: 240,
3799
+ maxWidth: 360,
3800
+ maxHeight: 320,
3801
+ overflowY: "auto",
3802
+ borderRadius: 8,
3803
+ border: "1px solid rgba(128,128,128,0.2)",
3804
+ boxShadow: "0 4px 24px rgba(0,0,0,0.12)",
3805
+ backdropFilter: "blur(16px)",
3806
+ WebkitBackdropFilter: "blur(16px)",
3807
+ backgroundColor: "rgba(128,128,128,0.08)",
3808
+ padding: "4px 0",
3809
+ marginTop: 4
3810
+ },
3811
+ children: [
3812
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { padding: "6px 12px", fontSize: 11, opacity: 0.5, fontWeight: 600 }, children: "Saved Filters" }),
3813
+ savedAIFilters.map((f, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3814
+ "div",
3815
+ {
3816
+ style: {
3817
+ display: "flex",
3818
+ alignItems: "center",
3819
+ gap: 6,
3820
+ padding: "6px 12px",
3821
+ cursor: "pointer",
3822
+ fontSize: 12
3823
+ },
3824
+ onMouseEnter: (e) => {
3825
+ e.currentTarget.style.backgroundColor = "rgba(128,128,128,0.15)";
3826
+ },
3827
+ onMouseLeave: (e) => {
3828
+ e.currentTarget.style.backgroundColor = "transparent";
3829
+ },
3830
+ children: [
3831
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3832
+ "span",
3833
+ {
3834
+ style: { flex: "1 1 0%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
3835
+ onClick: () => applySavedFilter(f),
3836
+ children: f.label
3837
+ }
3838
+ ),
3839
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3840
+ "button",
3841
+ {
3842
+ type: "button",
3843
+ onClick: (e) => {
3844
+ e.stopPropagation();
3845
+ removeSavedFilter(i);
3846
+ },
3847
+ style: {
3848
+ display: "flex",
3849
+ alignItems: "center",
3850
+ background: "none",
3851
+ border: "none",
3852
+ cursor: "pointer",
3853
+ padding: 2,
3854
+ color: "GrayText",
3855
+ flexShrink: 0
3856
+ },
3857
+ title: "Remove",
3858
+ children: icons?.close ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(XIcon, { style: { width: 10, height: 10 } })
3859
+ }
3860
+ )
3861
+ ]
3862
+ },
3863
+ i
3864
+ ))
3865
+ ]
3866
+ }
3867
+ )
3868
+ ] }),
3869
+ aiMode && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3870
+ "button",
3871
+ {
3872
+ type: "button",
3873
+ onClick: () => setAiBarOpen(true),
3874
+ style: {
3875
+ display: "flex",
3876
+ alignItems: "center",
3877
+ justifyContent: "center",
3878
+ gap: 4,
3879
+ background: `linear-gradient(135deg, ${accentColor}18, ${accentColor}08)`,
3880
+ border: `1px solid ${accentColor}40`,
3881
+ borderRadius: 4,
3882
+ cursor: "pointer",
3883
+ padding: "4px 8px",
3884
+ color: accentColor,
3885
+ fontSize: 12,
3886
+ fontWeight: 500,
3887
+ flexShrink: 0,
3888
+ transition: "all 0.2s ease"
3889
+ },
3890
+ title: "Ask AI",
3891
+ children: [
3892
+ icons?.sparkles ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SparklesIcon, { style: { width: 14, height: 14 } }),
3893
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: aiButtonLabel ?? "Ask AI" })
3894
+ ]
3895
+ }
3896
+ )
3897
+ ]
3898
+ }
3899
+ ),
3900
+ aiMode && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3901
+ "div",
3902
+ {
3903
+ style: {
3904
+ position: "absolute",
3905
+ inset: 0,
3906
+ display: "flex",
3907
+ alignItems: "center",
3908
+ gap: 8,
3909
+ padding: "4px 8px",
3910
+ opacity: aiBarOpen ? 1 : 0,
3911
+ transform: aiBarOpen ? "translateX(0)" : "translateX(40px)",
3912
+ transition: "opacity 0.3s cubic-bezier(0.4,0,0.2,1), transform 0.3s cubic-bezier(0.4,0,0.2,1)",
3913
+ pointerEvents: aiBarOpen ? "auto" : "none",
3914
+ zIndex: 2,
3915
+ background: "inherit",
3916
+ backdropFilter: "blur(12px)",
3917
+ WebkitBackdropFilter: "blur(12px)"
3010
3918
  },
3011
3919
  children: [
3012
3920
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3013
3921
  "span",
3014
3922
  {
3015
- style: { display: "flex", color: "GrayText", flexShrink: 0 },
3016
- children: icons?.search ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SearchIcon, { style: { width: 14, height: 14 } })
3923
+ style: {
3924
+ display: "flex",
3925
+ color: accentColor,
3926
+ flexShrink: 0
3927
+ },
3928
+ children: icons?.sparkles ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SparklesIcon, { style: { width: 16, height: 16 } })
3017
3929
  }
3018
3930
  ),
3019
3931
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3020
3932
  "input",
3021
3933
  {
3934
+ ref: aiInputRef,
3022
3935
  type: "text",
3023
- placeholder: "Search all columns...",
3024
- value: globalSearchValue ?? internalGlobalSearch,
3025
- onChange: (e) => {
3026
- const v = e.target.value;
3027
- if (onGlobalSearchChange) onGlobalSearchChange(v);
3028
- else setInternalGlobalSearch(v);
3936
+ placeholder: aiPlaceholder,
3937
+ value: aiQuery,
3938
+ onChange: (e) => setAiQuery(e.target.value),
3939
+ onKeyDown: (e) => {
3940
+ if (e.key === "Enter" && !aiLoading) handleAISubmit();
3941
+ if (e.key === "Escape") handleAIBarClose();
3029
3942
  },
3943
+ disabled: aiLoading,
3030
3944
  style: {
3031
3945
  flex: "1 1 0%",
3032
3946
  border: "none",
@@ -3035,18 +3949,51 @@ function BoltTable({
3035
3949
  font: "inherit",
3036
3950
  color: "inherit",
3037
3951
  padding: "4px 6px",
3038
- minWidth: 0
3952
+ minWidth: 0,
3953
+ fontSize: 12
3039
3954
  }
3040
3955
  }
3041
3956
  ),
3042
- (globalSearchValue ?? internalGlobalSearch) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3957
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3043
3958
  "button",
3044
3959
  {
3045
3960
  type: "button",
3046
- onClick: () => {
3047
- if (onGlobalSearchChange) onGlobalSearchChange("");
3048
- else setInternalGlobalSearch("");
3961
+ onClick: handleAISubmit,
3962
+ disabled: aiLoading || !aiQuery.trim(),
3963
+ style: {
3964
+ display: "flex",
3965
+ alignItems: "center",
3966
+ justifyContent: "center",
3967
+ background: aiQuery.trim() ? accentColor : "rgba(128,128,128,0.15)",
3968
+ border: "none",
3969
+ borderRadius: 4,
3970
+ cursor: aiLoading || !aiQuery.trim() ? "not-allowed" : "pointer",
3971
+ padding: "4px 8px",
3972
+ color: aiQuery.trim() ? "#fff" : "GrayText",
3973
+ transition: "all 0.2s ease",
3974
+ flexShrink: 0,
3975
+ gap: 4,
3976
+ fontSize: 12,
3977
+ opacity: aiLoading ? 0.7 : 1
3049
3978
  },
3979
+ title: "Send",
3980
+ children: aiLoading ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: icons?.loader ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3981
+ LoaderIcon,
3982
+ {
3983
+ style: {
3984
+ width: 14,
3985
+ height: 14,
3986
+ animation: "bt-spin 1s linear infinite"
3987
+ }
3988
+ }
3989
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: icons?.send ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SendIcon, { style: { width: 14, height: 14 } }) })
3990
+ }
3991
+ ),
3992
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3993
+ "button",
3994
+ {
3995
+ type: "button",
3996
+ onClick: handleAIBarClose,
3050
3997
  style: {
3051
3998
  display: "flex",
3052
3999
  alignItems: "center",
@@ -3058,114 +4005,127 @@ function BoltTable({
3058
4005
  color: "GrayText",
3059
4006
  flexShrink: 0
3060
4007
  },
3061
- children: icons?.close ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(XIcon, { style: { width: 12, height: 12 } })
4008
+ title: "Close AI",
4009
+ children: icons?.close ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(XIcon, { style: { width: 14, height: 14 } })
3062
4010
  }
3063
4011
  )
3064
4012
  ]
3065
4013
  }
4014
+ )
4015
+ ]
4016
+ }
4017
+ ),
4018
+ aiResult && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4019
+ "div",
4020
+ {
4021
+ style: {
4022
+ display: "flex",
4023
+ alignItems: "center",
4024
+ gap: 8,
4025
+ padding: "6px 12px",
4026
+ fontSize: 12,
4027
+ borderBottom: "1px solid rgba(128,128,128,0.2)",
4028
+ background: `linear-gradient(90deg, ${accentColor}08, transparent)`,
4029
+ flexShrink: 0
4030
+ },
4031
+ children: [
4032
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { color: accentColor, display: "flex", flexShrink: 0 }, children: icons?.sparkles ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SparklesIcon, { style: { width: 14, height: 14 } }) }),
4033
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { flex: "1 1 0%", opacity: 0.85 }, children: aiResult.message }),
4034
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4035
+ "button",
4036
+ {
4037
+ type: "button",
4038
+ onClick: saveCurrentAIFilter,
4039
+ style: {
4040
+ display: "flex",
4041
+ alignItems: "center",
4042
+ gap: 4,
4043
+ background: `${accentColor}12`,
4044
+ border: `1px solid ${accentColor}30`,
4045
+ borderRadius: 4,
4046
+ cursor: "pointer",
4047
+ padding: "2px 8px",
4048
+ color: accentColor,
4049
+ fontSize: 11,
4050
+ flexShrink: 0,
4051
+ fontWeight: 500,
4052
+ transition: "all 0.2s ease"
4053
+ },
4054
+ title: "Save this filter for quick access later",
4055
+ children: [
4056
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
4057
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" }),
4058
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "17 21 17 13 7 13 7 21" }),
4059
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "7 3 7 8 15 8" })
4060
+ ] }),
4061
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Save Filter" })
4062
+ ]
4063
+ }
3066
4064
  ),
3067
- toolbarContent,
3068
- showColumnSettings && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { position: "relative", flexShrink: 0 }, children: [
3069
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3070
- "button",
3071
- {
3072
- type: "button",
3073
- onClick: () => setShowColumnPicker((p) => !p),
3074
- style: {
3075
- display: "flex",
3076
- alignItems: "center",
3077
- justifyContent: "center",
3078
- background: "none",
3079
- border: "1px solid rgba(128,128,128,0.2)",
3080
- borderRadius: 4,
3081
- cursor: "pointer",
3082
- padding: "4px 6px",
3083
- color: "inherit",
3084
- gap: 4,
3085
- fontSize: 12
3086
- },
3087
- title: "Column settings",
3088
- children: [
3089
- icons?.columns ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ColumnsIcon, { style: { width: 14, height: 14 } }),
3090
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: columnSettingsLabel ?? "Columns" })
3091
- ]
3092
- }
3093
- ),
3094
- showColumnPicker && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3095
- "div",
3096
- {
3097
- ref: columnPickerRef,
3098
- style: {
3099
- position: "absolute",
3100
- top: "100%",
3101
- right: 0,
3102
- zIndex: 99999,
3103
- minWidth: 200,
3104
- maxHeight: 320,
3105
- overflowY: "auto",
3106
- borderRadius: 8,
3107
- border: "1px solid rgba(128,128,128,0.2)",
3108
- boxShadow: "0 4px 24px rgba(0,0,0,0.12)",
3109
- backdropFilter: "blur(16px)",
3110
- WebkitBackdropFilter: "blur(16px)",
3111
- backgroundColor: "rgba(128,128,128,0.08)",
3112
- padding: "4px 0",
3113
- marginTop: 4
3114
- },
3115
- children: initialColumns.filter(
3116
- (c) => c.key !== "__select__" && c.key !== "__expand__"
3117
- ).map((col) => {
3118
- const current = columns.find((c) => c.key === col.key);
3119
- const isHidden = current?.hidden ?? false;
3120
- const isPinned = !!current?.pinned;
3121
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3122
- "label",
3123
- {
3124
- style: {
3125
- display: "flex",
3126
- alignItems: "center",
3127
- gap: 8,
3128
- padding: "6px 12px",
3129
- cursor: isPinned ? "not-allowed" : "pointer",
3130
- opacity: isPinned ? 0.5 : 1,
3131
- fontSize: 12
3132
- },
3133
- children: [
3134
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3135
- "input",
3136
- {
3137
- type: "checkbox",
3138
- checked: !isHidden,
3139
- disabled: isPinned,
3140
- onChange: () => {
3141
- if (isPinned) return;
3142
- handleToggleHide(col.key);
3143
- },
3144
- style: {
3145
- cursor: isPinned ? "not-allowed" : "pointer",
3146
- accentColor
3147
- }
3148
- }
3149
- ),
3150
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3151
- "span",
3152
- {
3153
- style: {
3154
- overflow: "hidden",
3155
- textOverflow: "ellipsis",
3156
- whiteSpace: "nowrap"
3157
- },
3158
- children: typeof col.title === "string" ? col.title : col.key
3159
- }
3160
- )
3161
- ]
3162
- },
3163
- col.key
3164
- );
3165
- })
3166
- }
3167
- )
3168
- ] })
4065
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4066
+ "button",
4067
+ {
4068
+ type: "button",
4069
+ onClick: handleAIClear,
4070
+ style: {
4071
+ display: "flex",
4072
+ alignItems: "center",
4073
+ gap: 4,
4074
+ background: "none",
4075
+ border: "1px solid rgba(128,128,128,0.2)",
4076
+ borderRadius: 4,
4077
+ cursor: "pointer",
4078
+ padding: "2px 8px",
4079
+ color: "inherit",
4080
+ fontSize: 11,
4081
+ flexShrink: 0,
4082
+ opacity: 0.7
4083
+ },
4084
+ title: "Clear AI results",
4085
+ children: [
4086
+ icons?.close ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(XIcon, { style: { width: 10, height: 10 } }),
4087
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Clear" })
4088
+ ]
4089
+ }
4090
+ )
4091
+ ]
4092
+ }
4093
+ ),
4094
+ aiError && !aiResult && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4095
+ "div",
4096
+ {
4097
+ style: {
4098
+ display: "flex",
4099
+ alignItems: "center",
4100
+ gap: 8,
4101
+ padding: "6px 12px",
4102
+ fontSize: 12,
4103
+ borderBottom: "1px solid rgba(239,68,68,0.2)",
4104
+ background: "rgba(239,68,68,0.06)",
4105
+ color: "#ef4444",
4106
+ flexShrink: 0
4107
+ },
4108
+ children: [
4109
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { flex: "1 1 0%" }, children: aiError }),
4110
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4111
+ "button",
4112
+ {
4113
+ type: "button",
4114
+ onClick: () => setAiError(null),
4115
+ style: {
4116
+ display: "flex",
4117
+ alignItems: "center",
4118
+ justifyContent: "center",
4119
+ background: "none",
4120
+ border: "none",
4121
+ cursor: "pointer",
4122
+ padding: 2,
4123
+ color: "#ef4444",
4124
+ flexShrink: 0
4125
+ },
4126
+ children: icons?.close ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(XIcon, { style: { width: 12, height: 12 } })
4127
+ }
4128
+ )
3169
4129
  ]
3170
4130
  }
3171
4131
  ),
@@ -3667,14 +4627,20 @@ function BoltTable({
3667
4627
  gridTemplateColumns,
3668
4628
  headerHeight: HEADER_HEIGHT,
3669
4629
  rowClassName,
3670
- rowStyle,
4630
+ rowStyle: aiStyleOps.length > 0 ? (record, index) => {
4631
+ const base = rowStyle ? rowStyle(record, index) : void 0;
4632
+ const ai = getAIRowStyleForRecord(record);
4633
+ if (!base && !ai) return {};
4634
+ return { ...base, ...ai };
4635
+ } : rowStyle,
3671
4636
  bodyGridRow: hasColumnGroups ? 3 : 2,
3672
4637
  onEdit,
3673
4638
  editingCell,
3674
4639
  onEditComplete: handleEditComplete,
3675
4640
  enableDynamicRowHeight,
3676
4641
  onRowHeightChange: handleRowHeightChange,
3677
- columnGridIndexMap
4642
+ columnGridIndexMap,
4643
+ cellStyleFn: aiCellStyleOps.length > 0 ? (record, columnKey) => getAICellStyleForRecord(record, columnKey) : void 0
3678
4644
  }
3679
4645
  )
3680
4646
  ]
@@ -4292,10 +5258,16 @@ function BoltTable({
4292
5258
  })()
4293
5259
  ] });
4294
5260
  }
5261
+
5262
+ // src/types.ts
5263
+ function defineConfig(config) {
5264
+ return config;
5265
+ }
4295
5266
  // Annotate the CommonJS export names for ESM import in node:
4296
5267
  0 && (module.exports = {
4297
5268
  BoltTable,
4298
5269
  DraggableHeader,
4299
5270
  ResizeOverlay,
4300
- TableBody
5271
+ TableBody,
5272
+ defineConfig
4301
5273
  });