react-open-source-grid 1.6.4 → 1.6.6

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/lib/index.js CHANGED
@@ -2356,7 +2356,16 @@ var GridBody = ({
2356
2356
  onKeyDown: (e) => handleKeyDown(e, rowIndex, columnIndex),
2357
2357
  tabIndex: isCellFocused ? 0 : -1
2358
2358
  },
2359
- isEditing ? /* @__PURE__ */ React6.createElement(
2359
+ isEditing ? column.editor ? column.editor({
2360
+ value: editState.value,
2361
+ row,
2362
+ column,
2363
+ onChange: handleEditChange,
2364
+ onCommit: handleEditComplete,
2365
+ onCancel: () => dispatch({ type: "END_EDIT" }),
2366
+ autoFocus: true,
2367
+ ...column.editorParams
2368
+ }) : /* @__PURE__ */ React6.createElement(
2360
2369
  "input",
2361
2370
  {
2362
2371
  ref: editInputRef,
@@ -5027,7 +5036,7 @@ var LayoutPresetsManager = ({
5027
5036
  setLoading(false);
5028
5037
  }
5029
5038
  };
5030
- const formatDate = (timestamp) => {
5039
+ const formatDate2 = (timestamp) => {
5031
5040
  return new Date(timestamp).toLocaleString();
5032
5041
  };
5033
5042
  return /* @__PURE__ */ React14.createElement("div", { className: "relative inline-block" }, /* @__PURE__ */ React14.createElement(
@@ -5141,7 +5150,7 @@ var LayoutPresetsManager = ({
5141
5150
  if (buttons) buttons.style.opacity = "0";
5142
5151
  }
5143
5152
  },
5144
- /* @__PURE__ */ React14.createElement("div", { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between" } }, /* @__PURE__ */ React14.createElement("div", { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React14.createElement("h4", { style: { fontSize: "14px", fontWeight: "500", color: "#111827", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, preset.name), preset.description && /* @__PURE__ */ React14.createElement("p", { style: { fontSize: "12px", color: "#6b7280", marginTop: "4px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, preset.description), /* @__PURE__ */ React14.createElement("p", { style: { fontSize: "12px", color: "#9ca3af", marginTop: "4px" } }, "Updated: ", formatDate(preset.updatedAt))), /* @__PURE__ */ React14.createElement("div", { className: "preset-actions", style: { display: "flex", gap: "4px", marginLeft: "8px", opacity: 0, transition: "opacity 0.2s" } }, /* @__PURE__ */ React14.createElement(
5153
+ /* @__PURE__ */ React14.createElement("div", { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between" } }, /* @__PURE__ */ React14.createElement("div", { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React14.createElement("h4", { style: { fontSize: "14px", fontWeight: "500", color: "#111827", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, preset.name), preset.description && /* @__PURE__ */ React14.createElement("p", { style: { fontSize: "12px", color: "#6b7280", marginTop: "4px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, preset.description), /* @__PURE__ */ React14.createElement("p", { style: { fontSize: "12px", color: "#9ca3af", marginTop: "4px" } }, "Updated: ", formatDate2(preset.updatedAt))), /* @__PURE__ */ React14.createElement("div", { className: "preset-actions", style: { display: "flex", gap: "4px", marginLeft: "8px", opacity: 0, transition: "opacity 0.2s" } }, /* @__PURE__ */ React14.createElement(
5145
5154
  "button",
5146
5155
  {
5147
5156
  onClick: (e) => handleUpdatePreset(preset, e),
@@ -8043,6 +8052,195 @@ var GridApiImpl = class {
8043
8052
  }
8044
8053
  };
8045
8054
 
8055
+ // src/components/DataGrid/pivotEngine.ts
8056
+ var AGGREGATORS = {
8057
+ sum: (values) => values.reduce((acc, val) => acc + val, 0),
8058
+ count: (values) => values.length,
8059
+ avg: (values) => values.length > 0 ? values.reduce((acc, val) => acc + val, 0) / values.length : 0,
8060
+ min: (values) => values.length > 0 ? Math.min(...values) : 0,
8061
+ max: (values) => values.length > 0 ? Math.max(...values) : 0
8062
+ };
8063
+ function getUniqueValues(data, column) {
8064
+ const uniqueSet = /* @__PURE__ */ new Set();
8065
+ data.forEach((row) => {
8066
+ const value = row[column];
8067
+ if (value !== null && value !== void 0) {
8068
+ uniqueSet.add(String(value));
8069
+ }
8070
+ });
8071
+ return Array.from(uniqueSet).sort();
8072
+ }
8073
+ function getAggregatorFn(aggregator) {
8074
+ if (typeof aggregator === "function") {
8075
+ return aggregator;
8076
+ }
8077
+ return AGGREGATORS[aggregator] || AGGREGATORS.sum;
8078
+ }
8079
+ function toNumber(value) {
8080
+ if (typeof value === "number") return value;
8081
+ if (typeof value === "string") {
8082
+ const num = parseFloat(value);
8083
+ return isNaN(num) ? 0 : num;
8084
+ }
8085
+ return 0;
8086
+ }
8087
+ function buildPivot(data, config) {
8088
+ const {
8089
+ pivotColumn,
8090
+ valueColumn,
8091
+ rowGroupColumn,
8092
+ aggregator,
8093
+ showTotals = false,
8094
+ showGrandTotal = false
8095
+ } = config;
8096
+ if (!data || data.length === 0) {
8097
+ return {
8098
+ columns: [],
8099
+ rows: [],
8100
+ pivotValues: []
8101
+ };
8102
+ }
8103
+ const pivotValues = getUniqueValues(data, pivotColumn);
8104
+ const rowGroupValues = getUniqueValues(data, rowGroupColumn);
8105
+ const aggregatorFn = getAggregatorFn(aggregator);
8106
+ const groupedData = /* @__PURE__ */ new Map();
8107
+ rowGroupValues.forEach((rowValue) => {
8108
+ const pivotMap = /* @__PURE__ */ new Map();
8109
+ pivotValues.forEach((pivotValue) => {
8110
+ pivotMap.set(pivotValue, []);
8111
+ });
8112
+ groupedData.set(rowValue, pivotMap);
8113
+ });
8114
+ data.forEach((row) => {
8115
+ const rowValue = String(row[rowGroupColumn] ?? "");
8116
+ const pivotValue = String(row[pivotColumn] ?? "");
8117
+ const value = toNumber(row[valueColumn]);
8118
+ if (groupedData.has(rowValue)) {
8119
+ const pivotMap = groupedData.get(rowValue);
8120
+ if (pivotMap.has(pivotValue)) {
8121
+ pivotMap.get(pivotValue).push(value);
8122
+ }
8123
+ }
8124
+ });
8125
+ const columns = [
8126
+ {
8127
+ field: rowGroupColumn,
8128
+ headerName: formatHeaderName(rowGroupColumn),
8129
+ width: 180,
8130
+ sortable: true,
8131
+ filterable: true,
8132
+ isPivotColumn: false
8133
+ }
8134
+ ];
8135
+ pivotValues.forEach((pivotValue) => {
8136
+ columns.push({
8137
+ field: pivotValue,
8138
+ headerName: pivotValue,
8139
+ width: 120,
8140
+ sortable: true,
8141
+ filterable: true,
8142
+ isPivotColumn: true
8143
+ });
8144
+ });
8145
+ if (showGrandTotal) {
8146
+ columns.push({
8147
+ field: "__grandTotal",
8148
+ headerName: "Grand Total",
8149
+ width: 120,
8150
+ sortable: true,
8151
+ filterable: false,
8152
+ isTotalColumn: true
8153
+ });
8154
+ }
8155
+ const rows = [];
8156
+ rowGroupValues.forEach((rowValue) => {
8157
+ const pivotMap = groupedData.get(rowValue);
8158
+ const row = {
8159
+ [rowGroupColumn]: rowValue,
8160
+ __id: rowValue
8161
+ // Add unique ID for grid
8162
+ };
8163
+ let grandTotal = 0;
8164
+ pivotValues.forEach((pivotValue) => {
8165
+ const values = pivotMap.get(pivotValue);
8166
+ const aggregatedValue = aggregatorFn(values);
8167
+ row[pivotValue] = aggregatedValue;
8168
+ grandTotal += aggregatedValue;
8169
+ });
8170
+ if (showGrandTotal) {
8171
+ row["__grandTotal"] = grandTotal;
8172
+ }
8173
+ rows.push(row);
8174
+ });
8175
+ let totalsRow;
8176
+ if (showTotals) {
8177
+ totalsRow = {
8178
+ [rowGroupColumn]: "Total",
8179
+ __id: "__totals",
8180
+ __isTotal: true
8181
+ };
8182
+ let overallGrandTotal = 0;
8183
+ pivotValues.forEach((pivotValue) => {
8184
+ const allValues = [];
8185
+ groupedData.forEach((pivotMap) => {
8186
+ const values = pivotMap.get(pivotValue);
8187
+ allValues.push(...values);
8188
+ });
8189
+ const total = aggregatorFn(allValues);
8190
+ totalsRow[pivotValue] = total;
8191
+ overallGrandTotal += total;
8192
+ });
8193
+ if (showGrandTotal) {
8194
+ totalsRow["__grandTotal"] = overallGrandTotal;
8195
+ }
8196
+ }
8197
+ return {
8198
+ columns,
8199
+ rows,
8200
+ pivotValues,
8201
+ totalsRow
8202
+ };
8203
+ }
8204
+ function formatHeaderName(field) {
8205
+ return field.split(/(?=[A-Z])|_|-/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
8206
+ }
8207
+ function exportPivotToCSV(pivotResult) {
8208
+ const { columns, rows, totalsRow } = pivotResult;
8209
+ const headers = columns.map((col) => col.headerName).join(",");
8210
+ const dataRows = rows.map((row) => {
8211
+ return columns.map((col) => {
8212
+ const value = row[col.field];
8213
+ if (typeof value === "string" && value.includes(",")) {
8214
+ return `"${value}"`;
8215
+ }
8216
+ return value ?? "";
8217
+ }).join(",");
8218
+ });
8219
+ if (totalsRow) {
8220
+ const totalRow = columns.map((col) => {
8221
+ const value = totalsRow[col.field];
8222
+ if (typeof value === "string" && value.includes(",")) {
8223
+ return `"${value}"`;
8224
+ }
8225
+ return value ?? "";
8226
+ }).join(",");
8227
+ dataRows.push(totalRow);
8228
+ }
8229
+ return [headers, ...dataRows].join("\n");
8230
+ }
8231
+ function downloadCSV(csvContent, filename = "pivot-table.csv") {
8232
+ const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
8233
+ const link = document.createElement("a");
8234
+ const url = URL.createObjectURL(blob);
8235
+ link.setAttribute("href", url);
8236
+ link.setAttribute("download", filename);
8237
+ link.style.visibility = "hidden";
8238
+ document.body.appendChild(link);
8239
+ link.click();
8240
+ document.body.removeChild(link);
8241
+ URL.revokeObjectURL(url);
8242
+ }
8243
+
8046
8244
  // src/components/DataGrid/DataGrid.tsx
8047
8245
  var DataGrid = forwardRef(({
8048
8246
  columns,
@@ -8057,6 +8255,7 @@ var DataGrid = forwardRef(({
8057
8255
  rowPinConfig,
8058
8256
  contextMenuConfig,
8059
8257
  tooltipConfig,
8258
+ pivotConfig,
8060
8259
  tableId,
8061
8260
  theme: _theme = "quartz",
8062
8261
  densityMode: _densityMode = "normal",
@@ -8077,6 +8276,42 @@ var DataGrid = forwardRef(({
8077
8276
  );
8078
8277
  const [internalRows, setInternalRows] = useState14(null);
8079
8278
  const activeRows = internalRows !== null ? internalRows : rows;
8279
+ const { pivotedData, effectiveColumns } = useMemo4(() => {
8280
+ if (!pivotConfig) {
8281
+ return { pivotedData: activeRows, effectiveColumns: columns };
8282
+ }
8283
+ const pivotResult = buildPivot(activeRows, pivotConfig);
8284
+ console.log("Pivot Result:", {
8285
+ columnsCount: pivotResult.columns.length,
8286
+ columns: pivotResult.columns.map((c) => c.field),
8287
+ rowsCount: pivotResult.rows.length,
8288
+ sampleRow: pivotResult.rows[0]
8289
+ });
8290
+ const pivotColumns = pivotResult.columns.map((col) => ({
8291
+ field: col.field,
8292
+ headerName: col.headerName,
8293
+ width: col.width || 120,
8294
+ sortable: col.sortable !== false,
8295
+ filterable: col.filterable !== false,
8296
+ editable: false,
8297
+ renderCell: col.isTotalColumn || col.isPivotColumn ? (row) => {
8298
+ const value = row[col.field];
8299
+ if (typeof value === "number") {
8300
+ return /* @__PURE__ */ React20.createElement("span", { style: {
8301
+ fontWeight: col.isTotalColumn ? "700" : "600",
8302
+ color: col.isTotalColumn ? "#0f766e" : "#475569"
8303
+ } }, value.toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 }));
8304
+ }
8305
+ return value;
8306
+ } : void 0
8307
+ }));
8308
+ const allRows = pivotResult.totalsRow ? [...pivotResult.rows, pivotResult.totalsRow] : pivotResult.rows;
8309
+ const rowsWithId = allRows.map((row, index) => ({
8310
+ ...row,
8311
+ id: row.__id || row.id || `pivot-row-${index}`
8312
+ }));
8313
+ return { pivotedData: rowsWithId, effectiveColumns: pivotColumns };
8314
+ }, [activeRows, columns, pivotConfig]);
8080
8315
  const rowsRef = useRef10(rows);
8081
8316
  useEffect9(() => {
8082
8317
  if (rows !== rowsRef.current) {
@@ -8179,8 +8414,8 @@ var DataGrid = forwardRef(({
8179
8414
  gridApiRef.current = new GridApiImpl(
8180
8415
  state,
8181
8416
  dispatch,
8182
- columns,
8183
- activeRows,
8417
+ effectiveColumns,
8418
+ pivotedData,
8184
8419
  containerRef,
8185
8420
  persistenceManager,
8186
8421
  setInternalRows
@@ -8190,24 +8425,24 @@ var DataGrid = forwardRef(({
8190
8425
  useEffect9(() => {
8191
8426
  if (gridApiRef.current && !gridApiRef.current.isDestroyed()) {
8192
8427
  gridApiRef.current.updateState(state);
8193
- gridApiRef.current.updateData(columns, activeRows);
8428
+ gridApiRef.current.updateData(effectiveColumns, pivotedData);
8194
8429
  gridApiRef.current.updateCallbacks(setInternalRows);
8195
8430
  }
8196
- }, [state, columns, activeRows, setInternalRows]);
8431
+ }, [state, effectiveColumns, pivotedData, setInternalRows]);
8197
8432
  useImperativeHandle(ref, () => {
8198
8433
  if (!gridApiRef.current || gridApiRef.current.isDestroyed()) {
8199
8434
  gridApiRef.current = new GridApiImpl(
8200
8435
  state,
8201
8436
  dispatch,
8202
- columns,
8203
- activeRows,
8437
+ effectiveColumns,
8438
+ pivotedData,
8204
8439
  containerRef,
8205
8440
  persistenceManager,
8206
8441
  setInternalRows
8207
8442
  );
8208
8443
  }
8209
8444
  return gridApiRef.current;
8210
- }, [state, columns, activeRows, persistenceManager]);
8445
+ }, [state, effectiveColumns, pivotedData, persistenceManager]);
8211
8446
  const onGridReadyCalledRef = useRef10(false);
8212
8447
  const onGridReadyCallbackRef = useRef10(onGridReady);
8213
8448
  useEffect9(() => {
@@ -8288,9 +8523,11 @@ var DataGrid = forwardRef(({
8288
8523
  ...middleColumns,
8289
8524
  ...pinnedRightFields
8290
8525
  ];
8526
+ console.log("Display column order:", displayColumnOrder, "from state.columnOrder:", state.columnOrder);
8291
8527
  useEffect9(() => {
8292
- dispatch({ type: "RESET_COLUMNS", payload: columns });
8293
- }, [columns]);
8528
+ console.log("Dispatching RESET_COLUMNS with", effectiveColumns.length, "columns:", effectiveColumns.map((c) => c.field));
8529
+ dispatch({ type: "RESET_COLUMNS", payload: effectiveColumns });
8530
+ }, [effectiveColumns]);
8294
8531
  useEffect9(() => {
8295
8532
  if (onSelectionChange) {
8296
8533
  onSelectionChange(Array.from(state.selection.selectedRows));
@@ -8306,17 +8543,17 @@ var DataGrid = forwardRef(({
8306
8543
  }, [state.pinnedRowsTop, state.pinnedRowsBottom]);
8307
8544
  useEffect9(() => {
8308
8545
  if (state.sortConfig.field) {
8309
- const column = columns.find((c) => c.field === state.sortConfig.field);
8546
+ const column = effectiveColumns.find((c) => c.field === state.sortConfig.field);
8310
8547
  if (column) {
8311
8548
  announceSorting(column.headerName, state.sortConfig.direction);
8312
8549
  }
8313
8550
  }
8314
- }, [state.sortConfig.field, state.sortConfig.direction]);
8551
+ }, [state.sortConfig.field, state.sortConfig.direction, effectiveColumns]);
8315
8552
  const sortedRows = useMemo4(() => {
8316
8553
  if (!state.sortConfig.field || !state.sortConfig.direction) {
8317
- return activeRows;
8554
+ return pivotedData;
8318
8555
  }
8319
- const sorted = [...activeRows].sort((a, b) => {
8556
+ const sorted = [...pivotedData].sort((a, b) => {
8320
8557
  const aValue = a[state.sortConfig.field];
8321
8558
  const bValue = b[state.sortConfig.field];
8322
8559
  if (aValue == null && bValue == null) return 0;
@@ -8333,7 +8570,7 @@ var DataGrid = forwardRef(({
8333
8570
  sorted.reverse();
8334
8571
  }
8335
8572
  return sorted;
8336
- }, [activeRows, state.sortConfig]);
8573
+ }, [pivotedData, state.sortConfig]);
8337
8574
  const filteredRows = useMemo4(() => {
8338
8575
  if (!hasActiveFilters(state.filterConfig)) {
8339
8576
  return sortedRows;
@@ -8479,7 +8716,7 @@ var DataGrid = forwardRef(({
8479
8716
  /* @__PURE__ */ React20.createElement("div", { style: { position: "relative", display: "flex", alignItems: "center", justifyContent: "space-between", paddingLeft: "16px", paddingRight: "16px", paddingTop: "10px", paddingBottom: "10px", backgroundColor: "var(--grid-bg-alt)", borderBottom: "var(--grid-border-width, 1px) solid var(--grid-border)", zIndex: 30 } }, /* @__PURE__ */ React20.createElement("div", { style: { position: "relative", display: "flex", alignItems: "center", gap: "8px" } }, /* @__PURE__ */ React20.createElement(
8480
8717
  ColumnChooser,
8481
8718
  {
8482
- columns,
8719
+ columns: effectiveColumns,
8483
8720
  columnOrder: state.columnOrder,
8484
8721
  hiddenColumns: state.hiddenColumns,
8485
8722
  onToggleVisibility: (field) => dispatch({ type: "TOGGLE_COLUMN_VISIBILITY", payload: field }),
@@ -8489,7 +8726,7 @@ var DataGrid = forwardRef(({
8489
8726
  ), /* @__PURE__ */ React20.createElement(
8490
8727
  ExportMenu,
8491
8728
  {
8492
- columns,
8729
+ columns: effectiveColumns,
8493
8730
  fullDataset: rows,
8494
8731
  filteredData: filteredRows.filter((r) => !("isGroup" in r)),
8495
8732
  selectedRows: state.selection.selectedRows,
@@ -8507,7 +8744,7 @@ var DataGrid = forwardRef(({
8507
8744
  /* @__PURE__ */ React20.createElement(
8508
8745
  GroupByPanel,
8509
8746
  {
8510
- columns,
8747
+ columns: effectiveColumns,
8511
8748
  groupBy: state.groupBy,
8512
8749
  dispatch
8513
8750
  }
@@ -8515,7 +8752,7 @@ var DataGrid = forwardRef(({
8515
8752
  /* @__PURE__ */ React20.createElement("div", { role: "rowgroup", style: { position: "sticky", top: 0, zIndex: 20, width: "100%" } }, /* @__PURE__ */ React20.createElement(
8516
8753
  GridHeader,
8517
8754
  {
8518
- columns,
8755
+ columns: effectiveColumns,
8519
8756
  columnOrder: state.columnOrder,
8520
8757
  displayColumnOrder,
8521
8758
  columnWidths: state.columnWidths,
@@ -8534,7 +8771,7 @@ var DataGrid = forwardRef(({
8534
8771
  ), /* @__PURE__ */ React20.createElement(
8535
8772
  ColumnFilters,
8536
8773
  {
8537
- columns,
8774
+ columns: effectiveColumns,
8538
8775
  displayColumnOrder,
8539
8776
  columnWidths: state.columnWidths,
8540
8777
  filterConfig: state.filterConfig,
@@ -8547,7 +8784,7 @@ var DataGrid = forwardRef(({
8547
8784
  /* @__PURE__ */ React20.createElement(
8548
8785
  GridBody,
8549
8786
  {
8550
- columns,
8787
+ columns: effectiveColumns,
8551
8788
  rows: (virtualScrollConfig == null ? void 0 : virtualScrollConfig.enabled) ? unpinnedRows : paginatedRows,
8552
8789
  pinnedRowsTop: pinnedRowsTopData,
8553
8790
  pinnedRowsBottom: pinnedRowsBottomData,
@@ -8587,7 +8824,7 @@ var DataGrid = forwardRef(({
8587
8824
  (footerConfig == null ? void 0 : footerConfig.show) && footerConfig.aggregates && /* @__PURE__ */ React20.createElement(
8588
8825
  GridFooter,
8589
8826
  {
8590
- columns,
8827
+ columns: effectiveColumns,
8591
8828
  displayColumnOrder,
8592
8829
  columnWidths: state.columnWidths,
8593
8830
  aggregates: globalAggregates,
@@ -11752,6 +11989,1532 @@ function createMockFeed(config) {
11752
11989
  createConnection: () => createMockWebSocket(feed)
11753
11990
  };
11754
11991
  }
11992
+
11993
+ // src/components/DataGrid/PivotToolbar.tsx
11994
+ import React29, { useState as useState20, useMemo as useMemo8 } from "react";
11995
+ var AGGREGATOR_OPTIONS = [
11996
+ { value: "sum", label: "Sum" },
11997
+ { value: "avg", label: "Average" },
11998
+ { value: "count", label: "Count" },
11999
+ { value: "min", label: "Minimum" },
12000
+ { value: "max", label: "Maximum" }
12001
+ ];
12002
+ var PivotToolbar = ({
12003
+ columns,
12004
+ pivotConfig,
12005
+ onPivotToggle,
12006
+ onConfigChange,
12007
+ isPivotMode = false,
12008
+ style,
12009
+ className
12010
+ }) => {
12011
+ const [isExpanded, setIsExpanded] = useState20(isPivotMode);
12012
+ const [rowGroupColumn, setRowGroupColumn] = useState20((pivotConfig == null ? void 0 : pivotConfig.rowGroupColumn) || "");
12013
+ const [pivotColumn, setPivotColumn] = useState20((pivotConfig == null ? void 0 : pivotConfig.pivotColumn) || "");
12014
+ const [valueColumn, setValueColumn] = useState20((pivotConfig == null ? void 0 : pivotConfig.valueColumn) || "");
12015
+ const [aggregator, setAggregator] = useState20(
12016
+ (pivotConfig == null ? void 0 : pivotConfig.aggregator) || "sum"
12017
+ );
12018
+ const [showTotals, setShowTotals] = useState20((pivotConfig == null ? void 0 : pivotConfig.showTotals) ?? true);
12019
+ const [showGrandTotal, setShowGrandTotal] = useState20((pivotConfig == null ? void 0 : pivotConfig.showGrandTotal) ?? true);
12020
+ const isConfigValid = useMemo8(() => {
12021
+ return rowGroupColumn && pivotColumn && valueColumn;
12022
+ }, [rowGroupColumn, pivotColumn, valueColumn]);
12023
+ const handleApply = () => {
12024
+ if (!isConfigValid) return;
12025
+ const config = {
12026
+ rowGroupColumn,
12027
+ pivotColumn,
12028
+ valueColumn,
12029
+ aggregator,
12030
+ showTotals,
12031
+ showGrandTotal
12032
+ };
12033
+ onConfigChange(config);
12034
+ onPivotToggle(true);
12035
+ };
12036
+ const handleClear = () => {
12037
+ setRowGroupColumn("");
12038
+ setPivotColumn("");
12039
+ setValueColumn("");
12040
+ setAggregator("sum");
12041
+ setShowTotals(true);
12042
+ setShowGrandTotal(true);
12043
+ onConfigChange(null);
12044
+ onPivotToggle(false);
12045
+ setIsExpanded(false);
12046
+ };
12047
+ const handleToggle = () => {
12048
+ setIsExpanded(!isExpanded);
12049
+ };
12050
+ return /* @__PURE__ */ React29.createElement(
12051
+ "div",
12052
+ {
12053
+ className,
12054
+ style: {
12055
+ backgroundColor: "#f8fafc",
12056
+ border: "1px solid #e2e8f0",
12057
+ borderRadius: "8px",
12058
+ padding: "12px",
12059
+ ...style
12060
+ }
12061
+ },
12062
+ /* @__PURE__ */ React29.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: isExpanded ? "12px" : "0" } }, /* @__PURE__ */ React29.createElement("div", { style: { display: "flex", alignItems: "center", gap: "8px" } }, /* @__PURE__ */ React29.createElement("span", { style: { fontSize: "20px" } }, "\u{1F4CA}"), /* @__PURE__ */ React29.createElement("h3", { style: { margin: 0, fontSize: "14px", fontWeight: "600", color: "#1e293b" } }, "Pivot Table"), isPivotMode && /* @__PURE__ */ React29.createElement("span", { style: {
12063
+ backgroundColor: "#10b981",
12064
+ color: "white",
12065
+ padding: "2px 8px",
12066
+ borderRadius: "12px",
12067
+ fontSize: "11px",
12068
+ fontWeight: "600"
12069
+ } }, "Active")), /* @__PURE__ */ React29.createElement("div", { style: { display: "flex", gap: "8px" } }, isPivotMode && /* @__PURE__ */ React29.createElement(
12070
+ "button",
12071
+ {
12072
+ onClick: handleClear,
12073
+ style: {
12074
+ padding: "6px 12px",
12075
+ fontSize: "13px",
12076
+ fontWeight: "500",
12077
+ color: "#dc2626",
12078
+ backgroundColor: "#fee2e2",
12079
+ border: "1px solid #fecaca",
12080
+ borderRadius: "6px",
12081
+ cursor: "pointer",
12082
+ transition: "all 0.15s"
12083
+ },
12084
+ onMouseEnter: (e) => {
12085
+ e.currentTarget.style.backgroundColor = "#fecaca";
12086
+ },
12087
+ onMouseLeave: (e) => {
12088
+ e.currentTarget.style.backgroundColor = "#fee2e2";
12089
+ }
12090
+ },
12091
+ "Clear Pivot"
12092
+ ), /* @__PURE__ */ React29.createElement(
12093
+ "button",
12094
+ {
12095
+ onClick: handleToggle,
12096
+ style: {
12097
+ padding: "6px 10px",
12098
+ fontSize: "13px",
12099
+ color: "#64748b",
12100
+ backgroundColor: "white",
12101
+ border: "1px solid #cbd5e1",
12102
+ borderRadius: "6px",
12103
+ cursor: "pointer",
12104
+ transition: "all 0.15s"
12105
+ },
12106
+ onMouseEnter: (e) => {
12107
+ e.currentTarget.style.backgroundColor = "#f1f5f9";
12108
+ },
12109
+ onMouseLeave: (e) => {
12110
+ e.currentTarget.style.backgroundColor = "white";
12111
+ }
12112
+ },
12113
+ isExpanded ? "\u25B2" : "\u25BC"
12114
+ ))),
12115
+ isExpanded && /* @__PURE__ */ React29.createElement("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))", gap: "12px" } }, /* @__PURE__ */ React29.createElement("div", null, /* @__PURE__ */ React29.createElement("label", { style: { display: "block", fontSize: "12px", fontWeight: "600", color: "#475569", marginBottom: "6px" } }, "Row Group By ", /* @__PURE__ */ React29.createElement("span", { style: { color: "#dc2626" } }, "*")), /* @__PURE__ */ React29.createElement(
12116
+ "select",
12117
+ {
12118
+ value: rowGroupColumn,
12119
+ onChange: (e) => setRowGroupColumn(e.target.value),
12120
+ style: {
12121
+ width: "100%",
12122
+ padding: "8px",
12123
+ fontSize: "13px",
12124
+ border: "1px solid #cbd5e1",
12125
+ borderRadius: "6px",
12126
+ backgroundColor: "white",
12127
+ color: "#1e293b",
12128
+ cursor: "pointer"
12129
+ }
12130
+ },
12131
+ /* @__PURE__ */ React29.createElement("option", { value: "" }, "Select column..."),
12132
+ columns.map((col) => /* @__PURE__ */ React29.createElement("option", { key: col.field, value: col.field }, col.headerName))
12133
+ )), /* @__PURE__ */ React29.createElement("div", null, /* @__PURE__ */ React29.createElement("label", { style: { display: "block", fontSize: "12px", fontWeight: "600", color: "#475569", marginBottom: "6px" } }, "Pivot Column ", /* @__PURE__ */ React29.createElement("span", { style: { color: "#dc2626" } }, "*")), /* @__PURE__ */ React29.createElement(
12134
+ "select",
12135
+ {
12136
+ value: pivotColumn,
12137
+ onChange: (e) => setPivotColumn(e.target.value),
12138
+ style: {
12139
+ width: "100%",
12140
+ padding: "8px",
12141
+ fontSize: "13px",
12142
+ border: "1px solid #cbd5e1",
12143
+ borderRadius: "6px",
12144
+ backgroundColor: "white",
12145
+ color: "#1e293b",
12146
+ cursor: "pointer"
12147
+ }
12148
+ },
12149
+ /* @__PURE__ */ React29.createElement("option", { value: "" }, "Select column..."),
12150
+ columns.map((col) => /* @__PURE__ */ React29.createElement("option", { key: col.field, value: col.field }, col.headerName))
12151
+ )), /* @__PURE__ */ React29.createElement("div", null, /* @__PURE__ */ React29.createElement("label", { style: { display: "block", fontSize: "12px", fontWeight: "600", color: "#475569", marginBottom: "6px" } }, "Value Column ", /* @__PURE__ */ React29.createElement("span", { style: { color: "#dc2626" } }, "*")), /* @__PURE__ */ React29.createElement(
12152
+ "select",
12153
+ {
12154
+ value: valueColumn,
12155
+ onChange: (e) => setValueColumn(e.target.value),
12156
+ style: {
12157
+ width: "100%",
12158
+ padding: "8px",
12159
+ fontSize: "13px",
12160
+ border: "1px solid #cbd5e1",
12161
+ borderRadius: "6px",
12162
+ backgroundColor: "white",
12163
+ color: "#1e293b",
12164
+ cursor: "pointer"
12165
+ }
12166
+ },
12167
+ /* @__PURE__ */ React29.createElement("option", { value: "" }, "Select column..."),
12168
+ columns.map((col) => /* @__PURE__ */ React29.createElement("option", { key: col.field, value: col.field }, col.headerName))
12169
+ )), /* @__PURE__ */ React29.createElement("div", null, /* @__PURE__ */ React29.createElement("label", { style: { display: "block", fontSize: "12px", fontWeight: "600", color: "#475569", marginBottom: "6px" } }, "Aggregation"), /* @__PURE__ */ React29.createElement(
12170
+ "select",
12171
+ {
12172
+ value: aggregator,
12173
+ onChange: (e) => setAggregator(e.target.value),
12174
+ style: {
12175
+ width: "100%",
12176
+ padding: "8px",
12177
+ fontSize: "13px",
12178
+ border: "1px solid #cbd5e1",
12179
+ borderRadius: "6px",
12180
+ backgroundColor: "white",
12181
+ color: "#1e293b",
12182
+ cursor: "pointer"
12183
+ }
12184
+ },
12185
+ AGGREGATOR_OPTIONS.map((opt) => /* @__PURE__ */ React29.createElement("option", { key: opt.value, value: opt.value }, opt.label))
12186
+ )), /* @__PURE__ */ React29.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "8px", justifyContent: "center" } }, /* @__PURE__ */ React29.createElement("label", { style: { display: "flex", alignItems: "center", gap: "6px", fontSize: "13px", color: "#475569", cursor: "pointer" } }, /* @__PURE__ */ React29.createElement(
12187
+ "input",
12188
+ {
12189
+ type: "checkbox",
12190
+ checked: showTotals,
12191
+ onChange: (e) => setShowTotals(e.target.checked),
12192
+ style: { cursor: "pointer" }
12193
+ }
12194
+ ), "Show Totals Row"), /* @__PURE__ */ React29.createElement("label", { style: { display: "flex", alignItems: "center", gap: "6px", fontSize: "13px", color: "#475569", cursor: "pointer" } }, /* @__PURE__ */ React29.createElement(
12195
+ "input",
12196
+ {
12197
+ type: "checkbox",
12198
+ checked: showGrandTotal,
12199
+ onChange: (e) => setShowGrandTotal(e.target.checked),
12200
+ style: { cursor: "pointer" }
12201
+ }
12202
+ ), "Show Grand Total Column")), /* @__PURE__ */ React29.createElement("div", { style: { display: "flex", alignItems: "flex-end" } }, /* @__PURE__ */ React29.createElement(
12203
+ "button",
12204
+ {
12205
+ onClick: handleApply,
12206
+ disabled: !isConfigValid,
12207
+ style: {
12208
+ width: "100%",
12209
+ padding: "10px",
12210
+ fontSize: "14px",
12211
+ fontWeight: "600",
12212
+ color: "white",
12213
+ backgroundColor: isConfigValid ? "#2563eb" : "#94a3b8",
12214
+ border: "none",
12215
+ borderRadius: "6px",
12216
+ cursor: isConfigValid ? "pointer" : "not-allowed",
12217
+ transition: "all 0.15s",
12218
+ boxShadow: isConfigValid ? "0 2px 4px rgba(37, 99, 235, 0.2)" : "none"
12219
+ },
12220
+ onMouseEnter: (e) => {
12221
+ if (isConfigValid) {
12222
+ e.currentTarget.style.backgroundColor = "#1d4ed8";
12223
+ }
12224
+ },
12225
+ onMouseLeave: (e) => {
12226
+ if (isConfigValid) {
12227
+ e.currentTarget.style.backgroundColor = "#2563eb";
12228
+ }
12229
+ }
12230
+ },
12231
+ "Apply Pivot"
12232
+ ))),
12233
+ isExpanded && /* @__PURE__ */ React29.createElement("div", { style: {
12234
+ marginTop: "12px",
12235
+ padding: "10px",
12236
+ backgroundColor: "#eff6ff",
12237
+ border: "1px solid #bfdbfe",
12238
+ borderRadius: "6px",
12239
+ fontSize: "12px",
12240
+ color: "#1e40af"
12241
+ } }, /* @__PURE__ */ React29.createElement("strong", null, "\u{1F4A1} Tip:"), " Select a Row Group column to organize data by, a Pivot column whose values become new columns, and a Value column to aggregate.")
12242
+ );
12243
+ };
12244
+
12245
+ // src/editors/editorUtils.ts
12246
+ import { useEffect as useEffect14, useRef as useRef15, useCallback as useCallback11 } from "react";
12247
+ function useEditorKeyboardNavigation(handlers, config = {}) {
12248
+ const {
12249
+ commitOnEnter = true,
12250
+ cancelOnEscape = true,
12251
+ commitOnTab = true,
12252
+ preventDefault = true,
12253
+ stopPropagation = true
12254
+ } = config;
12255
+ const handleKeyDown = useCallback11(
12256
+ (event) => {
12257
+ let handled = false;
12258
+ switch (event.key) {
12259
+ case "Enter":
12260
+ if (commitOnEnter && handlers.onEnter) {
12261
+ handlers.onEnter();
12262
+ handled = true;
12263
+ }
12264
+ break;
12265
+ case "Escape":
12266
+ if (cancelOnEscape && handlers.onEscape) {
12267
+ handlers.onEscape();
12268
+ handled = true;
12269
+ }
12270
+ break;
12271
+ case "Tab":
12272
+ if (commitOnTab && handlers.onTab) {
12273
+ handlers.onTab(event.shiftKey);
12274
+ handled = true;
12275
+ }
12276
+ break;
12277
+ case "ArrowUp":
12278
+ if (handlers.onArrowUp) {
12279
+ handlers.onArrowUp();
12280
+ handled = true;
12281
+ }
12282
+ break;
12283
+ case "ArrowDown":
12284
+ if (handlers.onArrowDown) {
12285
+ handlers.onArrowDown();
12286
+ handled = true;
12287
+ }
12288
+ break;
12289
+ }
12290
+ if (handled) {
12291
+ if (preventDefault) {
12292
+ event.preventDefault();
12293
+ }
12294
+ if (stopPropagation) {
12295
+ event.stopPropagation();
12296
+ }
12297
+ }
12298
+ },
12299
+ [
12300
+ handlers,
12301
+ commitOnEnter,
12302
+ cancelOnEscape,
12303
+ commitOnTab,
12304
+ preventDefault,
12305
+ stopPropagation
12306
+ ]
12307
+ );
12308
+ return { handleKeyDown };
12309
+ }
12310
+ function useEditorAutoFocus(autoFocus = true, selectAll = false) {
12311
+ const elementRef = useRef15(null);
12312
+ useEffect14(() => {
12313
+ if (autoFocus && elementRef.current) {
12314
+ elementRef.current.focus();
12315
+ if (selectAll && elementRef.current instanceof HTMLInputElement) {
12316
+ elementRef.current.select();
12317
+ }
12318
+ }
12319
+ }, [autoFocus, selectAll]);
12320
+ return elementRef;
12321
+ }
12322
+ function useEditorClickOutside(ref, onClickOutside, enabled = true) {
12323
+ useEffect14(() => {
12324
+ if (!enabled) return;
12325
+ const handleClickOutside = (event) => {
12326
+ if (ref.current && !ref.current.contains(event.target)) {
12327
+ onClickOutside();
12328
+ }
12329
+ };
12330
+ const timeoutId = setTimeout(() => {
12331
+ document.addEventListener("mousedown", handleClickOutside);
12332
+ }, 0);
12333
+ return () => {
12334
+ clearTimeout(timeoutId);
12335
+ document.removeEventListener("mousedown", handleClickOutside);
12336
+ };
12337
+ }, [ref, onClickOutside, enabled]);
12338
+ }
12339
+ function usePopupPosition(anchorRef, popupRef, isOpen, placement = "auto") {
12340
+ useEffect14(() => {
12341
+ if (!isOpen || !anchorRef.current || !popupRef.current) return;
12342
+ const anchor = anchorRef.current;
12343
+ const popup = popupRef.current;
12344
+ const anchorRect = anchor.getBoundingClientRect();
12345
+ const popupRect = popup.getBoundingClientRect();
12346
+ const viewportHeight = window.innerHeight;
12347
+ const viewportWidth = window.innerWidth;
12348
+ let top = 0;
12349
+ let left = 0;
12350
+ let actualPlacement = placement;
12351
+ if (placement === "auto") {
12352
+ const spaceBelow = viewportHeight - anchorRect.bottom;
12353
+ const spaceAbove = anchorRect.top;
12354
+ const spaceRight = viewportWidth - anchorRect.right;
12355
+ const spaceLeft = anchorRect.left;
12356
+ if (spaceBelow >= popupRect.height || spaceBelow >= spaceAbove) {
12357
+ actualPlacement = "bottom";
12358
+ } else if (spaceAbove >= popupRect.height) {
12359
+ actualPlacement = "top";
12360
+ } else if (spaceRight >= popupRect.width) {
12361
+ actualPlacement = "right";
12362
+ } else if (spaceLeft >= popupRect.width) {
12363
+ actualPlacement = "left";
12364
+ } else {
12365
+ actualPlacement = "bottom";
12366
+ }
12367
+ }
12368
+ switch (actualPlacement) {
12369
+ case "bottom":
12370
+ top = anchorRect.bottom + window.scrollY;
12371
+ left = anchorRect.left + window.scrollX;
12372
+ break;
12373
+ case "top":
12374
+ top = anchorRect.top + window.scrollY - popupRect.height;
12375
+ left = anchorRect.left + window.scrollX;
12376
+ break;
12377
+ case "right":
12378
+ top = anchorRect.top + window.scrollY;
12379
+ left = anchorRect.right + window.scrollX;
12380
+ break;
12381
+ case "left":
12382
+ top = anchorRect.top + window.scrollY;
12383
+ left = anchorRect.left + window.scrollX - popupRect.width;
12384
+ break;
12385
+ }
12386
+ const margin = 8;
12387
+ if (left + popupRect.width > viewportWidth) {
12388
+ left = viewportWidth - popupRect.width - margin;
12389
+ }
12390
+ if (left < margin) {
12391
+ left = margin;
12392
+ }
12393
+ if (top + popupRect.height > viewportHeight + window.scrollY) {
12394
+ top = viewportHeight + window.scrollY - popupRect.height - margin;
12395
+ }
12396
+ if (top < window.scrollY + margin) {
12397
+ top = window.scrollY + margin;
12398
+ }
12399
+ popup.style.top = `${top}px`;
12400
+ popup.style.left = `${left}px`;
12401
+ popup.style.minWidth = `${anchorRect.width}px`;
12402
+ }, [isOpen, anchorRef, popupRef, placement]);
12403
+ }
12404
+ function debounce2(func, wait) {
12405
+ let timeout = null;
12406
+ return function executedFunction(...args) {
12407
+ const later = () => {
12408
+ timeout = null;
12409
+ func(...args);
12410
+ };
12411
+ if (timeout) {
12412
+ clearTimeout(timeout);
12413
+ }
12414
+ timeout = setTimeout(later, wait);
12415
+ };
12416
+ }
12417
+ function formatNumber(value, decimals = 0, thousandsSeparator = ",", decimalSeparator = ".") {
12418
+ const fixed = value.toFixed(decimals);
12419
+ const [integerPart, decimalPart] = fixed.split(".");
12420
+ const formattedInteger = integerPart.replace(
12421
+ /\B(?=(\d{3})+(?!\d))/g,
12422
+ thousandsSeparator
12423
+ );
12424
+ return decimalPart ? `${formattedInteger}${decimalSeparator}${decimalPart}` : formattedInteger;
12425
+ }
12426
+ function parseFormattedNumber(value, thousandsSeparator = ",", decimalSeparator = ".") {
12427
+ if (!value || typeof value !== "string") return null;
12428
+ const normalized = value.replace(new RegExp(`\\${thousandsSeparator}`, "g"), "").replace(new RegExp(`\\${decimalSeparator}`), ".");
12429
+ const parsed = parseFloat(normalized);
12430
+ return isNaN(parsed) ? null : parsed;
12431
+ }
12432
+ function filterOptions(options, searchQuery) {
12433
+ if (!searchQuery.trim()) return options;
12434
+ const lowerQuery = searchQuery.toLowerCase();
12435
+ return options.filter(
12436
+ (option) => option.label.toLowerCase().includes(lowerQuery)
12437
+ );
12438
+ }
12439
+
12440
+ // src/editors/RichSelectEditor.tsx
12441
+ import React30, { useState as useState21, useRef as useRef16, useEffect as useEffect15, useMemo as useMemo9 } from "react";
12442
+ function RichSelectEditor(props) {
12443
+ const {
12444
+ value,
12445
+ onChange,
12446
+ onCommit,
12447
+ onCancel,
12448
+ autoFocus = true,
12449
+ options,
12450
+ placeholder = "Select...",
12451
+ allowClear = false,
12452
+ filterable = true,
12453
+ renderOptionLabel,
12454
+ maxDropdownHeight = 300
12455
+ } = props;
12456
+ const [isOpen] = useState21(true);
12457
+ const [searchQuery, setSearchQuery] = useState21("");
12458
+ const [focusedIndex, setFocusedIndex] = useState21(-1);
12459
+ const containerRef = useRef16(null);
12460
+ const inputRef = useEditorAutoFocus(autoFocus, true);
12461
+ const dropdownRef = useRef16(null);
12462
+ const optionRefs = useRef16([]);
12463
+ const filteredOptions = useMemo9(
12464
+ () => filterable ? filterOptions(options, searchQuery) : options,
12465
+ [options, searchQuery, filterable]
12466
+ );
12467
+ const selectedOption = useMemo9(
12468
+ () => options.find((opt) => opt.value === value),
12469
+ [options, value]
12470
+ );
12471
+ useEffect15(() => {
12472
+ if (selectedOption) {
12473
+ const index = filteredOptions.findIndex(
12474
+ (opt) => opt.value === selectedOption.value
12475
+ );
12476
+ if (index !== -1 && focusedIndex === -1) {
12477
+ queueMicrotask(() => setFocusedIndex(index));
12478
+ }
12479
+ }
12480
+ }, [selectedOption, filteredOptions]);
12481
+ useEffect15(() => {
12482
+ var _a;
12483
+ if (focusedIndex >= 0 && optionRefs.current[focusedIndex]) {
12484
+ (_a = optionRefs.current[focusedIndex]) == null ? void 0 : _a.scrollIntoView({
12485
+ block: "nearest",
12486
+ behavior: "smooth"
12487
+ });
12488
+ }
12489
+ }, [focusedIndex]);
12490
+ usePopupPosition(containerRef, dropdownRef, isOpen, "auto");
12491
+ useEditorClickOutside(
12492
+ containerRef,
12493
+ () => {
12494
+ if (isOpen) {
12495
+ onCommit();
12496
+ }
12497
+ },
12498
+ true
12499
+ );
12500
+ const handleSelectOption = (option) => {
12501
+ if (!option.disabled) {
12502
+ onChange(option.value);
12503
+ onCommit();
12504
+ }
12505
+ };
12506
+ const handleClear = (e) => {
12507
+ e.stopPropagation();
12508
+ onChange(null);
12509
+ setSearchQuery("");
12510
+ setFocusedIndex(-1);
12511
+ };
12512
+ const handleArrowDown = () => {
12513
+ setFocusedIndex((prev) => {
12514
+ var _a;
12515
+ let next = prev + 1;
12516
+ while (next < filteredOptions.length && ((_a = filteredOptions[next]) == null ? void 0 : _a.disabled)) {
12517
+ next++;
12518
+ }
12519
+ return next < filteredOptions.length ? next : prev;
12520
+ });
12521
+ };
12522
+ const handleArrowUp = () => {
12523
+ setFocusedIndex((prev) => {
12524
+ var _a;
12525
+ let next = prev - 1;
12526
+ while (next >= 0 && ((_a = filteredOptions[next]) == null ? void 0 : _a.disabled)) {
12527
+ next--;
12528
+ }
12529
+ return next >= 0 ? next : prev;
12530
+ });
12531
+ };
12532
+ const handleEnter = () => {
12533
+ if (focusedIndex >= 0 && focusedIndex < filteredOptions.length) {
12534
+ const option = filteredOptions[focusedIndex];
12535
+ if (option && !option.disabled) {
12536
+ handleSelectOption(option);
12537
+ }
12538
+ } else {
12539
+ onCommit();
12540
+ }
12541
+ };
12542
+ const { handleKeyDown } = useEditorKeyboardNavigation(
12543
+ {
12544
+ onEnter: handleEnter,
12545
+ onEscape: onCancel,
12546
+ onArrowUp: handleArrowUp,
12547
+ onArrowDown: handleArrowDown
12548
+ },
12549
+ {
12550
+ commitOnTab: true,
12551
+ commitOnBlur: false
12552
+ }
12553
+ );
12554
+ const renderLabel = (option) => {
12555
+ if (renderOptionLabel) {
12556
+ return renderOptionLabel(option);
12557
+ }
12558
+ return /* @__PURE__ */ React30.createElement("div", { className: "editor-option-content" }, option.icon && /* @__PURE__ */ React30.createElement("span", { className: "editor-option-icon" }, option.icon), /* @__PURE__ */ React30.createElement("div", { className: "editor-option-text" }, /* @__PURE__ */ React30.createElement("div", { className: "editor-option-label" }, option.label), option.description && /* @__PURE__ */ React30.createElement("div", { className: "editor-option-description" }, option.description)));
12559
+ };
12560
+ return /* @__PURE__ */ React30.createElement(
12561
+ "div",
12562
+ {
12563
+ ref: containerRef,
12564
+ className: "editor-container editor-richselect-container",
12565
+ onKeyDown: handleKeyDown
12566
+ },
12567
+ /* @__PURE__ */ React30.createElement("div", { className: "editor-input-wrapper" }, /* @__PURE__ */ React30.createElement(
12568
+ "input",
12569
+ {
12570
+ ref: inputRef,
12571
+ type: "text",
12572
+ className: "editor-input editor-richselect-input",
12573
+ value: filterable ? searchQuery : (selectedOption == null ? void 0 : selectedOption.label) || "",
12574
+ onChange: (e) => {
12575
+ if (filterable) {
12576
+ setSearchQuery(e.target.value);
12577
+ setFocusedIndex(-1);
12578
+ }
12579
+ },
12580
+ placeholder: (selectedOption == null ? void 0 : selectedOption.label) || placeholder,
12581
+ "aria-label": "Select option",
12582
+ "aria-expanded": isOpen,
12583
+ "aria-autocomplete": "list",
12584
+ "aria-controls": "richselect-dropdown",
12585
+ autoComplete: "off"
12586
+ }
12587
+ ), /* @__PURE__ */ React30.createElement("div", { className: "editor-input-actions" }, allowClear && value !== null && value !== void 0 && /* @__PURE__ */ React30.createElement(
12588
+ "button",
12589
+ {
12590
+ type: "button",
12591
+ className: "editor-clear-btn",
12592
+ onClick: handleClear,
12593
+ "aria-label": "Clear selection",
12594
+ tabIndex: -1
12595
+ },
12596
+ "\xD7"
12597
+ ), /* @__PURE__ */ React30.createElement("span", { className: "editor-dropdown-icon", "aria-hidden": "true" }, "\u25BC"))),
12598
+ isOpen && /* @__PURE__ */ React30.createElement(
12599
+ "div",
12600
+ {
12601
+ ref: dropdownRef,
12602
+ id: "richselect-dropdown",
12603
+ className: "editor-dropdown",
12604
+ role: "listbox",
12605
+ style: { maxHeight: maxDropdownHeight }
12606
+ },
12607
+ filteredOptions.length === 0 ? /* @__PURE__ */ React30.createElement("div", { className: "editor-dropdown-empty" }, "No options found") : filteredOptions.map((option, index) => /* @__PURE__ */ React30.createElement(
12608
+ "div",
12609
+ {
12610
+ key: option.value,
12611
+ ref: (el) => {
12612
+ optionRefs.current[index] = el;
12613
+ },
12614
+ className: `editor-dropdown-option ${option.value === value ? "selected" : ""} ${option.disabled ? "disabled" : ""} ${index === focusedIndex ? "focused" : ""}`,
12615
+ role: "option",
12616
+ "aria-selected": option.value === value,
12617
+ "aria-disabled": option.disabled,
12618
+ onClick: () => handleSelectOption(option),
12619
+ onMouseEnter: () => !option.disabled && setFocusedIndex(index)
12620
+ },
12621
+ renderLabel(option)
12622
+ ))
12623
+ )
12624
+ );
12625
+ }
12626
+ RichSelectEditor.displayName = "RichSelectEditor";
12627
+
12628
+ // src/editors/DateEditor.tsx
12629
+ import React31, { useState as useState22, useRef as useRef17, useMemo as useMemo10 } from "react";
12630
+ function DateEditor(props) {
12631
+ const {
12632
+ value,
12633
+ onChange,
12634
+ onCommit,
12635
+ onCancel,
12636
+ autoFocus = true,
12637
+ dateFormat = "yyyy-MM-dd",
12638
+ showTime = false,
12639
+ minDate,
12640
+ maxDate,
12641
+ autoCommit = false
12642
+ } = props;
12643
+ const [isOpen] = useState22(true);
12644
+ const [inputValue, setInputValue] = useState22("");
12645
+ const [viewDate, setViewDate] = useState22(/* @__PURE__ */ new Date());
12646
+ const containerRef = useRef17(null);
12647
+ const inputRef = useEditorAutoFocus(autoFocus, true);
12648
+ const calendarRef = useRef17(null);
12649
+ const parsedValue = useMemo10(() => {
12650
+ if (!value) return null;
12651
+ if (value instanceof Date) return value;
12652
+ const parsed = new Date(value);
12653
+ return isNaN(parsed.getTime()) ? null : parsed;
12654
+ }, [value]);
12655
+ React31.useEffect(() => {
12656
+ if (parsedValue) {
12657
+ setInputValue(formatDate(parsedValue, dateFormat, showTime));
12658
+ setViewDate(parsedValue);
12659
+ }
12660
+ }, [parsedValue, dateFormat, showTime]);
12661
+ usePopupPosition(containerRef, calendarRef, isOpen, "auto");
12662
+ useEditorClickOutside(containerRef, () => {
12663
+ if (isOpen) {
12664
+ onCommit();
12665
+ }
12666
+ }, true);
12667
+ const handleSelectDate = (date) => {
12668
+ const newDate = new Date(date);
12669
+ if (parsedValue && !showTime) {
12670
+ newDate.setHours(parsedValue.getHours());
12671
+ newDate.setMinutes(parsedValue.getMinutes());
12672
+ newDate.setSeconds(parsedValue.getSeconds());
12673
+ }
12674
+ onChange(newDate);
12675
+ setInputValue(formatDate(newDate, dateFormat, showTime));
12676
+ if (autoCommit && !showTime) {
12677
+ onCommit();
12678
+ }
12679
+ };
12680
+ const handleTimeChange = (hours, minutes) => {
12681
+ const newDate = parsedValue ? new Date(parsedValue) : /* @__PURE__ */ new Date();
12682
+ newDate.setHours(hours);
12683
+ newDate.setMinutes(minutes);
12684
+ onChange(newDate);
12685
+ setInputValue(formatDate(newDate, dateFormat, showTime));
12686
+ };
12687
+ const handleInputChange = (e) => {
12688
+ const newValue = e.target.value;
12689
+ setInputValue(newValue);
12690
+ const parsed = parseDate(newValue, dateFormat);
12691
+ if (parsed && !isNaN(parsed.getTime())) {
12692
+ onChange(parsed);
12693
+ setViewDate(parsed);
12694
+ }
12695
+ };
12696
+ const handleInputBlur = () => {
12697
+ if (parsedValue) {
12698
+ setInputValue(formatDate(parsedValue, dateFormat, showTime));
12699
+ }
12700
+ };
12701
+ const handlePrevMonth = () => {
12702
+ setViewDate((prev) => {
12703
+ const newDate = new Date(prev);
12704
+ newDate.setMonth(newDate.getMonth() - 1);
12705
+ return newDate;
12706
+ });
12707
+ };
12708
+ const handleNextMonth = () => {
12709
+ setViewDate((prev) => {
12710
+ const newDate = new Date(prev);
12711
+ newDate.setMonth(newDate.getMonth() + 1);
12712
+ return newDate;
12713
+ });
12714
+ };
12715
+ const { handleKeyDown } = useEditorKeyboardNavigation(
12716
+ {
12717
+ onEnter: onCommit,
12718
+ onEscape: onCancel
12719
+ },
12720
+ {
12721
+ commitOnTab: true,
12722
+ commitOnBlur: false
12723
+ }
12724
+ );
12725
+ const calendarDays = useMemo10(() => {
12726
+ return generateCalendarDays(viewDate, parsedValue, minDate, maxDate);
12727
+ }, [viewDate, parsedValue, minDate, maxDate]);
12728
+ return /* @__PURE__ */ React31.createElement(
12729
+ "div",
12730
+ {
12731
+ ref: containerRef,
12732
+ className: "editor-container editor-date-container",
12733
+ onKeyDown: handleKeyDown
12734
+ },
12735
+ /* @__PURE__ */ React31.createElement("div", { className: "editor-input-wrapper" }, /* @__PURE__ */ React31.createElement(
12736
+ "input",
12737
+ {
12738
+ ref: inputRef,
12739
+ type: "text",
12740
+ className: "editor-input editor-date-input",
12741
+ value: inputValue,
12742
+ onChange: handleInputChange,
12743
+ onBlur: handleInputBlur,
12744
+ placeholder: dateFormat,
12745
+ "aria-label": "Date input",
12746
+ autoComplete: "off"
12747
+ }
12748
+ ), /* @__PURE__ */ React31.createElement("span", { className: "editor-calendar-icon", "aria-hidden": "true" }, "\u{1F4C5}")),
12749
+ isOpen && /* @__PURE__ */ React31.createElement("div", { ref: calendarRef, className: "editor-dropdown editor-calendar" }, /* @__PURE__ */ React31.createElement("div", { className: "editor-calendar-header" }, /* @__PURE__ */ React31.createElement(
12750
+ "button",
12751
+ {
12752
+ type: "button",
12753
+ className: "editor-calendar-nav",
12754
+ onClick: handlePrevMonth,
12755
+ "aria-label": "Previous month"
12756
+ },
12757
+ "\u2039"
12758
+ ), /* @__PURE__ */ React31.createElement("div", { className: "editor-calendar-title" }, viewDate.toLocaleDateString("en-US", {
12759
+ month: "long",
12760
+ year: "numeric"
12761
+ })), /* @__PURE__ */ React31.createElement(
12762
+ "button",
12763
+ {
12764
+ type: "button",
12765
+ className: "editor-calendar-nav",
12766
+ onClick: handleNextMonth,
12767
+ "aria-label": "Next month"
12768
+ },
12769
+ "\u203A"
12770
+ )), /* @__PURE__ */ React31.createElement("div", { className: "editor-calendar-weekdays" }, ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"].map((day) => /* @__PURE__ */ React31.createElement("div", { key: day, className: "editor-calendar-weekday" }, day))), /* @__PURE__ */ React31.createElement("div", { className: "editor-calendar-days" }, calendarDays.map((day, index) => /* @__PURE__ */ React31.createElement(
12771
+ "button",
12772
+ {
12773
+ key: index,
12774
+ type: "button",
12775
+ className: `editor-calendar-day ${day.className}`,
12776
+ onClick: () => day.date && handleSelectDate(day.date),
12777
+ disabled: day.disabled,
12778
+ "aria-label": day.date ? day.date.toDateString() : ""
12779
+ },
12780
+ day.label
12781
+ ))), showTime && /* @__PURE__ */ React31.createElement("div", { className: "editor-calendar-time" }, /* @__PURE__ */ React31.createElement(
12782
+ "input",
12783
+ {
12784
+ type: "time",
12785
+ className: "editor-time-input",
12786
+ value: parsedValue ? `${String(parsedValue.getHours()).padStart(2, "0")}:${String(
12787
+ parsedValue.getMinutes()
12788
+ ).padStart(2, "0")}` : "00:00",
12789
+ onChange: (e) => {
12790
+ const [hours, minutes] = e.target.value.split(":").map(Number);
12791
+ handleTimeChange(hours, minutes);
12792
+ },
12793
+ "aria-label": "Time input"
12794
+ }
12795
+ )))
12796
+ );
12797
+ }
12798
+ DateEditor.displayName = "DateEditor";
12799
+ function formatDate(date, format, showTime) {
12800
+ const year = date.getFullYear();
12801
+ const month = String(date.getMonth() + 1).padStart(2, "0");
12802
+ const day = String(date.getDate()).padStart(2, "0");
12803
+ const hours = String(date.getHours()).padStart(2, "0");
12804
+ const minutes = String(date.getMinutes()).padStart(2, "0");
12805
+ let formatted = format.replace("yyyy", String(year)).replace("MM", month).replace("dd", day);
12806
+ if (showTime) {
12807
+ formatted += ` ${hours}:${minutes}`;
12808
+ }
12809
+ return formatted;
12810
+ }
12811
+ function parseDate(value, format) {
12812
+ if (!value) return null;
12813
+ try {
12814
+ const parts = value.split(/[-/\s:]/);
12815
+ if (parts.length < 3) return null;
12816
+ let year, month, day;
12817
+ let hours = 0, minutes = 0;
12818
+ if (format.startsWith("yyyy")) {
12819
+ year = parseInt(parts[0], 10);
12820
+ month = parseInt(parts[1], 10) - 1;
12821
+ day = parseInt(parts[2], 10);
12822
+ } else if (format.startsWith("MM")) {
12823
+ month = parseInt(parts[0], 10) - 1;
12824
+ day = parseInt(parts[1], 10);
12825
+ year = parseInt(parts[2], 10);
12826
+ } else {
12827
+ day = parseInt(parts[0], 10);
12828
+ month = parseInt(parts[1], 10) - 1;
12829
+ year = parseInt(parts[2], 10);
12830
+ }
12831
+ if (parts.length >= 5) {
12832
+ hours = parseInt(parts[3], 10);
12833
+ minutes = parseInt(parts[4], 10);
12834
+ }
12835
+ const date = new Date(year, month, day, hours, minutes);
12836
+ return isNaN(date.getTime()) ? null : date;
12837
+ } catch {
12838
+ return null;
12839
+ }
12840
+ }
12841
+ function generateCalendarDays(viewDate, selectedDate, minDate, maxDate) {
12842
+ const year = viewDate.getFullYear();
12843
+ const month = viewDate.getMonth();
12844
+ const firstDay = new Date(year, month, 1);
12845
+ const lastDay = new Date(year, month + 1, 0);
12846
+ const daysInMonth = lastDay.getDate();
12847
+ const startingDayOfWeek = firstDay.getDay();
12848
+ const days = [];
12849
+ const prevMonth = new Date(year, month, 0);
12850
+ const prevMonthDays = prevMonth.getDate();
12851
+ for (let i = startingDayOfWeek - 1; i >= 0; i--) {
12852
+ const date = new Date(year, month - 1, prevMonthDays - i);
12853
+ days.push({
12854
+ date,
12855
+ label: String(prevMonthDays - i),
12856
+ className: "other-month",
12857
+ disabled: isDateDisabled(date, minDate, maxDate)
12858
+ });
12859
+ }
12860
+ for (let i = 1; i <= daysInMonth; i++) {
12861
+ const date = new Date(year, month, i);
12862
+ const isSelected = selectedDate && isSameDay(date, selectedDate);
12863
+ const isToday = isSameDay(date, /* @__PURE__ */ new Date());
12864
+ const disabled = isDateDisabled(date, minDate, maxDate);
12865
+ days.push({
12866
+ date,
12867
+ label: String(i),
12868
+ className: `${isSelected ? "selected" : ""} ${isToday ? "today" : ""} ${disabled ? "disabled" : ""}`.trim(),
12869
+ disabled
12870
+ });
12871
+ }
12872
+ const remainingDays = 42 - days.length;
12873
+ for (let i = 1; i <= remainingDays; i++) {
12874
+ const date = new Date(year, month + 1, i);
12875
+ days.push({
12876
+ date,
12877
+ label: String(i),
12878
+ className: "other-month",
12879
+ disabled: isDateDisabled(date, minDate, maxDate)
12880
+ });
12881
+ }
12882
+ return days;
12883
+ }
12884
+ function isSameDay(date1, date2) {
12885
+ return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate();
12886
+ }
12887
+ function isDateDisabled(date, minDate, maxDate) {
12888
+ if (minDate && date < minDate) return true;
12889
+ if (maxDate && date > maxDate) return true;
12890
+ return false;
12891
+ }
12892
+
12893
+ // src/editors/NumericEditor.tsx
12894
+ import React32, { useState as useState23, useEffect as useEffect16 } from "react";
12895
+ function NumericEditor(props) {
12896
+ const {
12897
+ value,
12898
+ onChange,
12899
+ onCommit,
12900
+ onCancel,
12901
+ autoFocus = true,
12902
+ min,
12903
+ max,
12904
+ step = 1,
12905
+ decimals = 0,
12906
+ prefix = "",
12907
+ suffix = "",
12908
+ allowNegative = true,
12909
+ showSteppers = true,
12910
+ thousandsSeparator = ",",
12911
+ decimalSeparator = "."
12912
+ } = props;
12913
+ const [inputValue, setInputValue] = useState23("");
12914
+ const [isFocused, setIsFocused] = useState23(true);
12915
+ const inputRef = useEditorAutoFocus(autoFocus, true);
12916
+ useEffect16(() => {
12917
+ if (value !== null && value !== void 0) {
12918
+ if (isFocused) {
12919
+ queueMicrotask(() => setInputValue(String(value)));
12920
+ } else {
12921
+ queueMicrotask(() => setInputValue(formatNumber(value, decimals, thousandsSeparator, decimalSeparator)));
12922
+ }
12923
+ } else {
12924
+ queueMicrotask(() => setInputValue(""));
12925
+ }
12926
+ }, [value, isFocused, decimals, thousandsSeparator, decimalSeparator]);
12927
+ const clampValue = (val) => {
12928
+ let clamped = val;
12929
+ if (min !== void 0 && clamped < min) clamped = min;
12930
+ if (max !== void 0 && clamped > max) clamped = max;
12931
+ return clamped;
12932
+ };
12933
+ const handleInputChange = (e) => {
12934
+ const newValue = e.target.value;
12935
+ setInputValue(newValue);
12936
+ if (newValue === "" || newValue === "-") {
12937
+ onChange(null);
12938
+ return;
12939
+ }
12940
+ let cleaned = newValue;
12941
+ if (prefix) cleaned = cleaned.replace(prefix, "");
12942
+ if (suffix) cleaned = cleaned.replace(suffix, "");
12943
+ const parsed = parseFormattedNumber(cleaned, thousandsSeparator, decimalSeparator);
12944
+ if (parsed !== null && !isNaN(parsed)) {
12945
+ if (!allowNegative && parsed < 0) {
12946
+ return;
12947
+ }
12948
+ const clamped = clampValue(parsed);
12949
+ onChange(clamped);
12950
+ }
12951
+ };
12952
+ const handleIncrement = () => {
12953
+ const currentValue = value ?? 0;
12954
+ const newValue = clampValue(currentValue + step);
12955
+ onChange(newValue);
12956
+ setInputValue(String(newValue));
12957
+ };
12958
+ const handleDecrement = () => {
12959
+ const currentValue = value ?? 0;
12960
+ const newValue = clampValue(currentValue - step);
12961
+ onChange(newValue);
12962
+ setInputValue(String(newValue));
12963
+ };
12964
+ const handleFocus = () => {
12965
+ setIsFocused(true);
12966
+ if (value !== null && value !== void 0) {
12967
+ setInputValue(String(value));
12968
+ }
12969
+ };
12970
+ const handleBlur = () => {
12971
+ setIsFocused(false);
12972
+ if (value !== null && value !== void 0) {
12973
+ const formatted = formatNumber(value, decimals, thousandsSeparator, decimalSeparator);
12974
+ setInputValue(formatted);
12975
+ }
12976
+ setTimeout(() => onCommit(), 100);
12977
+ };
12978
+ const handleKeyDown = (e) => {
12979
+ if (e.key === "ArrowUp") {
12980
+ e.preventDefault();
12981
+ handleIncrement();
12982
+ } else if (e.key === "ArrowDown") {
12983
+ e.preventDefault();
12984
+ handleDecrement();
12985
+ }
12986
+ };
12987
+ const { handleKeyDown: handleEditorKeyDown } = useEditorKeyboardNavigation(
12988
+ {
12989
+ onEnter: onCommit,
12990
+ onEscape: onCancel
12991
+ },
12992
+ {
12993
+ commitOnTab: true,
12994
+ commitOnBlur: false,
12995
+ preventDefault: false
12996
+ // Let handleKeyDown handle arrow keys
12997
+ }
12998
+ );
12999
+ const handleCombinedKeyDown = (e) => {
13000
+ handleKeyDown(e);
13001
+ handleEditorKeyDown(e);
13002
+ };
13003
+ const displayValue = isFocused ? inputValue : inputValue ? `${prefix}${inputValue}${suffix}` : "";
13004
+ return /* @__PURE__ */ React32.createElement("div", { className: "editor-container editor-numeric-container" }, /* @__PURE__ */ React32.createElement("div", { className: "editor-input-wrapper" }, prefix && !isFocused && /* @__PURE__ */ React32.createElement("span", { className: "editor-numeric-prefix" }, prefix), /* @__PURE__ */ React32.createElement(
13005
+ "input",
13006
+ {
13007
+ ref: inputRef,
13008
+ type: "text",
13009
+ inputMode: "decimal",
13010
+ className: "editor-input editor-numeric-input",
13011
+ value: displayValue,
13012
+ onChange: handleInputChange,
13013
+ onFocus: handleFocus,
13014
+ onBlur: handleBlur,
13015
+ onKeyDown: handleCombinedKeyDown,
13016
+ "aria-label": "Numeric input",
13017
+ "aria-valuemin": min,
13018
+ "aria-valuemax": max,
13019
+ "aria-valuenow": value ?? void 0,
13020
+ autoComplete: "off"
13021
+ }
13022
+ ), suffix && !isFocused && /* @__PURE__ */ React32.createElement("span", { className: "editor-numeric-suffix" }, suffix), showSteppers && /* @__PURE__ */ React32.createElement("div", { className: "editor-numeric-steppers" }, /* @__PURE__ */ React32.createElement(
13023
+ "button",
13024
+ {
13025
+ type: "button",
13026
+ className: "editor-numeric-stepper editor-numeric-increment",
13027
+ onClick: handleIncrement,
13028
+ onMouseDown: (e) => e.preventDefault(),
13029
+ disabled: max !== void 0 && (value ?? 0) >= max,
13030
+ "aria-label": "Increment",
13031
+ tabIndex: -1
13032
+ },
13033
+ "\u25B2"
13034
+ ), /* @__PURE__ */ React32.createElement(
13035
+ "button",
13036
+ {
13037
+ type: "button",
13038
+ className: "editor-numeric-stepper editor-numeric-decrement",
13039
+ onClick: handleDecrement,
13040
+ onMouseDown: (e) => e.preventDefault(),
13041
+ disabled: min !== void 0 && (value ?? 0) <= min,
13042
+ "aria-label": "Decrement",
13043
+ tabIndex: -1
13044
+ },
13045
+ "\u25BC"
13046
+ ))), (min !== void 0 || max !== void 0) && /* @__PURE__ */ React32.createElement("div", { className: "editor-numeric-range" }, min !== void 0 && /* @__PURE__ */ React32.createElement("span", null, "Min: ", min), max !== void 0 && /* @__PURE__ */ React32.createElement("span", null, "Max: ", max)));
13047
+ }
13048
+ NumericEditor.displayName = "NumericEditor";
13049
+
13050
+ // src/editors/MultiSelectEditor.tsx
13051
+ import React33, { useState as useState24, useRef as useRef18, useMemo as useMemo11 } from "react";
13052
+ function MultiSelectEditor(props) {
13053
+ const {
13054
+ value = [],
13055
+ onChange,
13056
+ onCommit,
13057
+ onCancel,
13058
+ autoFocus = true,
13059
+ options,
13060
+ placeholder = "Select...",
13061
+ maxTagCount = 3,
13062
+ filterable = true,
13063
+ maxDropdownHeight = 300,
13064
+ allowEmpty = true
13065
+ } = props;
13066
+ const [isOpen] = useState24(true);
13067
+ const [searchQuery, setSearchQuery] = useState24("");
13068
+ const [focusedIndex, setFocusedIndex] = useState24(-1);
13069
+ const containerRef = useRef18(null);
13070
+ const inputRef = useEditorAutoFocus(autoFocus);
13071
+ const dropdownRef = useRef18(null);
13072
+ const tagContainerRef = useRef18(null);
13073
+ const selectedValues = useMemo11(
13074
+ () => Array.isArray(value) ? value : [],
13075
+ [value]
13076
+ );
13077
+ const filteredOptions = useMemo11(
13078
+ () => filterable ? filterOptions(options, searchQuery) : options,
13079
+ [options, searchQuery, filterable]
13080
+ );
13081
+ const selectedOptions = useMemo11(
13082
+ () => options.filter((opt) => selectedValues.includes(opt.value)),
13083
+ [options, selectedValues]
13084
+ );
13085
+ usePopupPosition(containerRef, dropdownRef, isOpen, "auto");
13086
+ useEditorClickOutside(
13087
+ containerRef,
13088
+ () => {
13089
+ if (isOpen) {
13090
+ onCommit();
13091
+ }
13092
+ },
13093
+ true
13094
+ );
13095
+ const handleToggleOption = (option) => {
13096
+ if (option.disabled) return;
13097
+ const isSelected = selectedValues.includes(option.value);
13098
+ let newValues;
13099
+ if (isSelected) {
13100
+ if (!allowEmpty && selectedValues.length === 1) {
13101
+ return;
13102
+ }
13103
+ newValues = selectedValues.filter((v) => v !== option.value);
13104
+ } else {
13105
+ newValues = [...selectedValues, option.value];
13106
+ }
13107
+ onChange(newValues);
13108
+ };
13109
+ const handleRemoveTag = (optionValue, e) => {
13110
+ e.stopPropagation();
13111
+ if (!allowEmpty && selectedValues.length === 1) {
13112
+ return;
13113
+ }
13114
+ const newValues = selectedValues.filter((v) => v !== optionValue);
13115
+ onChange(newValues);
13116
+ };
13117
+ const handleKeyDown = (e) => {
13118
+ if (e.key === "Backspace" && searchQuery === "" && selectedValues.length > 0) {
13119
+ e.preventDefault();
13120
+ if (allowEmpty || selectedValues.length > 1) {
13121
+ const newValues = selectedValues.slice(0, -1);
13122
+ onChange(newValues);
13123
+ }
13124
+ }
13125
+ };
13126
+ const handleArrowDown = () => {
13127
+ setFocusedIndex((prev) => {
13128
+ var _a;
13129
+ let next = prev + 1;
13130
+ while (next < filteredOptions.length && ((_a = filteredOptions[next]) == null ? void 0 : _a.disabled)) {
13131
+ next++;
13132
+ }
13133
+ return next < filteredOptions.length ? next : prev;
13134
+ });
13135
+ };
13136
+ const handleArrowUp = () => {
13137
+ setFocusedIndex((prev) => {
13138
+ var _a;
13139
+ let next = prev - 1;
13140
+ while (next >= 0 && ((_a = filteredOptions[next]) == null ? void 0 : _a.disabled)) {
13141
+ next--;
13142
+ }
13143
+ return next >= 0 ? next : prev;
13144
+ });
13145
+ };
13146
+ const handleEnter = () => {
13147
+ if (focusedIndex >= 0 && focusedIndex < filteredOptions.length) {
13148
+ const option = filteredOptions[focusedIndex];
13149
+ if (option && !option.disabled) {
13150
+ handleToggleOption(option);
13151
+ }
13152
+ } else {
13153
+ onCommit();
13154
+ }
13155
+ };
13156
+ const { handleKeyDown: handleEditorKeyDown } = useEditorKeyboardNavigation(
13157
+ {
13158
+ onEnter: handleEnter,
13159
+ onEscape: onCancel,
13160
+ onArrowUp: handleArrowUp,
13161
+ onArrowDown: handleArrowDown
13162
+ },
13163
+ {
13164
+ commitOnTab: true,
13165
+ commitOnBlur: false
13166
+ }
13167
+ );
13168
+ const handleCombinedKeyDown = (e) => {
13169
+ handleKeyDown(e);
13170
+ handleEditorKeyDown(e);
13171
+ };
13172
+ const visibleTags = selectedOptions.slice(0, maxTagCount);
13173
+ const collapsedCount = Math.max(0, selectedOptions.length - maxTagCount);
13174
+ return /* @__PURE__ */ React33.createElement(
13175
+ "div",
13176
+ {
13177
+ ref: containerRef,
13178
+ className: "editor-container editor-multiselect-container",
13179
+ onKeyDown: handleCombinedKeyDown
13180
+ },
13181
+ /* @__PURE__ */ React33.createElement(
13182
+ "div",
13183
+ {
13184
+ ref: tagContainerRef,
13185
+ className: "editor-input-wrapper editor-multiselect-wrapper"
13186
+ },
13187
+ /* @__PURE__ */ React33.createElement("div", { className: "editor-multiselect-tags" }, visibleTags.map((option) => /* @__PURE__ */ React33.createElement("div", { key: option.value, className: "editor-tag" }, option.icon && /* @__PURE__ */ React33.createElement("span", { className: "editor-tag-icon" }, option.icon), /* @__PURE__ */ React33.createElement("span", { className: "editor-tag-label" }, option.label), /* @__PURE__ */ React33.createElement(
13188
+ "button",
13189
+ {
13190
+ type: "button",
13191
+ className: "editor-tag-remove",
13192
+ onClick: (e) => handleRemoveTag(option.value, e),
13193
+ "aria-label": `Remove ${option.label}`,
13194
+ tabIndex: -1
13195
+ },
13196
+ "\xD7"
13197
+ ))), collapsedCount > 0 && /* @__PURE__ */ React33.createElement("div", { className: "editor-tag editor-tag-collapsed" }, "+", collapsedCount)),
13198
+ /* @__PURE__ */ React33.createElement(
13199
+ "input",
13200
+ {
13201
+ ref: inputRef,
13202
+ type: "text",
13203
+ className: "editor-input editor-multiselect-input",
13204
+ value: searchQuery,
13205
+ onChange: (e) => {
13206
+ setSearchQuery(e.target.value);
13207
+ setFocusedIndex(-1);
13208
+ },
13209
+ placeholder: selectedValues.length === 0 ? placeholder : "",
13210
+ "aria-label": "Search options",
13211
+ "aria-expanded": isOpen,
13212
+ "aria-autocomplete": "list",
13213
+ "aria-controls": "multiselect-dropdown",
13214
+ autoComplete: "off"
13215
+ }
13216
+ ),
13217
+ /* @__PURE__ */ React33.createElement("span", { className: "editor-dropdown-icon", "aria-hidden": "true" }, "\u25BC")
13218
+ ),
13219
+ isOpen && /* @__PURE__ */ React33.createElement(
13220
+ "div",
13221
+ {
13222
+ ref: dropdownRef,
13223
+ id: "multiselect-dropdown",
13224
+ className: "editor-dropdown",
13225
+ role: "listbox",
13226
+ "aria-multiselectable": "true",
13227
+ style: { maxHeight: maxDropdownHeight }
13228
+ },
13229
+ filteredOptions.length === 0 ? /* @__PURE__ */ React33.createElement("div", { className: "editor-dropdown-empty" }, "No options found") : filteredOptions.map((option, index) => {
13230
+ const isSelected = selectedValues.includes(option.value);
13231
+ return /* @__PURE__ */ React33.createElement(
13232
+ "div",
13233
+ {
13234
+ key: option.value,
13235
+ className: `editor-dropdown-option editor-multiselect-option ${isSelected ? "selected" : ""} ${option.disabled ? "disabled" : ""} ${index === focusedIndex ? "focused" : ""}`,
13236
+ role: "option",
13237
+ "aria-selected": isSelected,
13238
+ "aria-disabled": option.disabled,
13239
+ onClick: () => handleToggleOption(option),
13240
+ onMouseEnter: () => !option.disabled && setFocusedIndex(index)
13241
+ },
13242
+ /* @__PURE__ */ React33.createElement(
13243
+ "input",
13244
+ {
13245
+ type: "checkbox",
13246
+ className: "editor-multiselect-checkbox",
13247
+ checked: isSelected,
13248
+ onChange: () => {
13249
+ },
13250
+ disabled: option.disabled,
13251
+ tabIndex: -1,
13252
+ "aria-hidden": "true"
13253
+ }
13254
+ ),
13255
+ /* @__PURE__ */ React33.createElement("div", { className: "editor-option-content" }, option.icon && /* @__PURE__ */ React33.createElement("span", { className: "editor-option-icon" }, option.icon), /* @__PURE__ */ React33.createElement("span", { className: "editor-option-label" }, option.label))
13256
+ );
13257
+ })
13258
+ )
13259
+ );
13260
+ }
13261
+ MultiSelectEditor.displayName = "MultiSelectEditor";
13262
+
13263
+ // src/editors/MarkdownEditor.tsx
13264
+ import React34, { useState as useState25, useRef as useRef19, useMemo as useMemo12 } from "react";
13265
+ function MarkdownEditor(props) {
13266
+ const {
13267
+ value = "",
13268
+ onChange,
13269
+ onCommit,
13270
+ onCancel,
13271
+ autoFocus = true,
13272
+ maxLength,
13273
+ showPreview = true,
13274
+ minHeight = 150,
13275
+ maxHeight = 400,
13276
+ rows = 6
13277
+ } = props;
13278
+ const [internalValue, setInternalValue] = useState25(value || "");
13279
+ const [showPreviewPanel, setShowPreviewPanel] = useState25(showPreview);
13280
+ const containerRef = useRef19(null);
13281
+ const textareaRef = useEditorAutoFocus(autoFocus, true);
13282
+ useEditorClickOutside(
13283
+ containerRef,
13284
+ () => {
13285
+ onCommit();
13286
+ },
13287
+ true
13288
+ );
13289
+ const handleChange = (e) => {
13290
+ const newValue = e.target.value;
13291
+ if (maxLength && newValue.length > maxLength) {
13292
+ return;
13293
+ }
13294
+ setInternalValue(newValue);
13295
+ onChange(newValue);
13296
+ };
13297
+ const handleKeyDown = (e) => {
13298
+ if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
13299
+ e.preventDefault();
13300
+ onCommit();
13301
+ return;
13302
+ }
13303
+ if (e.key === "Escape") {
13304
+ e.preventDefault();
13305
+ onCancel();
13306
+ return;
13307
+ }
13308
+ if (e.key === "Tab") {
13309
+ e.preventDefault();
13310
+ const start = e.currentTarget.selectionStart;
13311
+ const end = e.currentTarget.selectionEnd;
13312
+ const newValue = internalValue.substring(0, start) + " " + internalValue.substring(end);
13313
+ setInternalValue(newValue);
13314
+ onChange(newValue);
13315
+ setTimeout(() => {
13316
+ if (textareaRef.current) {
13317
+ textareaRef.current.selectionStart = start + 1;
13318
+ textareaRef.current.selectionEnd = start + 1;
13319
+ }
13320
+ }, 0);
13321
+ return;
13322
+ }
13323
+ if (e.ctrlKey || e.metaKey) {
13324
+ let insertion = "";
13325
+ let cursorOffset = 0;
13326
+ switch (e.key) {
13327
+ case "b":
13328
+ insertion = "****";
13329
+ cursorOffset = 2;
13330
+ break;
13331
+ case "i":
13332
+ insertion = "__";
13333
+ cursorOffset = 1;
13334
+ break;
13335
+ case "k":
13336
+ insertion = "[](url)";
13337
+ cursorOffset = 1;
13338
+ break;
13339
+ default:
13340
+ return;
13341
+ }
13342
+ if (insertion) {
13343
+ e.preventDefault();
13344
+ const start = e.currentTarget.selectionStart;
13345
+ const end = e.currentTarget.selectionEnd;
13346
+ const selectedText = internalValue.substring(start, end);
13347
+ let newValue;
13348
+ let newCursorPos;
13349
+ if (selectedText) {
13350
+ const before = insertion.substring(0, cursorOffset);
13351
+ const after = insertion.substring(cursorOffset);
13352
+ newValue = internalValue.substring(0, start) + before + selectedText + after + internalValue.substring(end);
13353
+ newCursorPos = start + before.length + selectedText.length;
13354
+ } else {
13355
+ newValue = internalValue.substring(0, start) + insertion + internalValue.substring(end);
13356
+ newCursorPos = start + cursorOffset;
13357
+ }
13358
+ setInternalValue(newValue);
13359
+ onChange(newValue);
13360
+ setTimeout(() => {
13361
+ if (textareaRef.current) {
13362
+ textareaRef.current.selectionStart = newCursorPos;
13363
+ textareaRef.current.selectionEnd = newCursorPos;
13364
+ textareaRef.current.focus();
13365
+ }
13366
+ }, 0);
13367
+ }
13368
+ }
13369
+ };
13370
+ const charCount = internalValue.length;
13371
+ const charCountClass = maxLength && charCount > maxLength * 0.9 ? "warning" : "";
13372
+ const previewHtml = useMemo12(() => {
13373
+ return renderMarkdownPreview(internalValue);
13374
+ }, [internalValue]);
13375
+ return /* @__PURE__ */ React34.createElement(
13376
+ "div",
13377
+ {
13378
+ ref: containerRef,
13379
+ className: "editor-container editor-markdown-container",
13380
+ style: { minHeight }
13381
+ },
13382
+ /* @__PURE__ */ React34.createElement("div", { className: "editor-markdown-toolbar" }, /* @__PURE__ */ React34.createElement("div", { className: "editor-markdown-toolbar-group" }, /* @__PURE__ */ React34.createElement(
13383
+ "button",
13384
+ {
13385
+ type: "button",
13386
+ className: "editor-toolbar-btn",
13387
+ onClick: () => {
13388
+ if (textareaRef.current) {
13389
+ const start = textareaRef.current.selectionStart;
13390
+ const end = textareaRef.current.selectionEnd;
13391
+ const selectedText = internalValue.substring(start, end);
13392
+ const newValue = internalValue.substring(0, start) + `**${selectedText || "bold"}**` + internalValue.substring(end);
13393
+ setInternalValue(newValue);
13394
+ onChange(newValue);
13395
+ textareaRef.current.focus();
13396
+ }
13397
+ },
13398
+ title: "Bold (Ctrl+B)",
13399
+ "aria-label": "Bold"
13400
+ },
13401
+ /* @__PURE__ */ React34.createElement("strong", null, "B")
13402
+ ), /* @__PURE__ */ React34.createElement(
13403
+ "button",
13404
+ {
13405
+ type: "button",
13406
+ className: "editor-toolbar-btn",
13407
+ onClick: () => {
13408
+ if (textareaRef.current) {
13409
+ const start = textareaRef.current.selectionStart;
13410
+ const end = textareaRef.current.selectionEnd;
13411
+ const selectedText = internalValue.substring(start, end);
13412
+ const newValue = internalValue.substring(0, start) + `_${selectedText || "italic"}_` + internalValue.substring(end);
13413
+ setInternalValue(newValue);
13414
+ onChange(newValue);
13415
+ textareaRef.current.focus();
13416
+ }
13417
+ },
13418
+ title: "Italic (Ctrl+I)",
13419
+ "aria-label": "Italic"
13420
+ },
13421
+ /* @__PURE__ */ React34.createElement("em", null, "I")
13422
+ ), /* @__PURE__ */ React34.createElement(
13423
+ "button",
13424
+ {
13425
+ type: "button",
13426
+ className: "editor-toolbar-btn",
13427
+ onClick: () => {
13428
+ if (textareaRef.current) {
13429
+ const start = textareaRef.current.selectionStart;
13430
+ const end = textareaRef.current.selectionEnd;
13431
+ const selectedText = internalValue.substring(start, end);
13432
+ const newValue = internalValue.substring(0, start) + `[${selectedText || "link text"}](url)` + internalValue.substring(end);
13433
+ setInternalValue(newValue);
13434
+ onChange(newValue);
13435
+ textareaRef.current.focus();
13436
+ }
13437
+ },
13438
+ title: "Link (Ctrl+K)",
13439
+ "aria-label": "Link"
13440
+ },
13441
+ "\u{1F517}"
13442
+ )), /* @__PURE__ */ React34.createElement("div", { className: "editor-markdown-toolbar-group" }, /* @__PURE__ */ React34.createElement(
13443
+ "button",
13444
+ {
13445
+ type: "button",
13446
+ className: `editor-toolbar-btn ${showPreviewPanel ? "active" : ""}`,
13447
+ onClick: () => setShowPreviewPanel(!showPreviewPanel),
13448
+ title: "Toggle preview",
13449
+ "aria-label": "Toggle preview",
13450
+ "aria-pressed": showPreviewPanel
13451
+ },
13452
+ "\u{1F441}"
13453
+ ))),
13454
+ /* @__PURE__ */ React34.createElement("div", { className: `editor-markdown-content ${showPreviewPanel ? "split" : ""}` }, /* @__PURE__ */ React34.createElement("div", { className: "editor-markdown-editor" }, /* @__PURE__ */ React34.createElement(
13455
+ "textarea",
13456
+ {
13457
+ ref: textareaRef,
13458
+ className: "editor-textarea editor-markdown-textarea",
13459
+ value: internalValue,
13460
+ onChange: handleChange,
13461
+ onKeyDown: handleKeyDown,
13462
+ rows,
13463
+ maxLength,
13464
+ placeholder: "Enter markdown text...",
13465
+ "aria-label": "Markdown editor",
13466
+ style: { maxHeight }
13467
+ }
13468
+ ), /* @__PURE__ */ React34.createElement("div", { className: `editor-markdown-char-count ${charCountClass}` }, charCount, maxLength && ` / ${maxLength}`)), showPreviewPanel && /* @__PURE__ */ React34.createElement("div", { className: "editor-markdown-preview" }, /* @__PURE__ */ React34.createElement("div", { className: "editor-markdown-preview-label" }, "Preview"), /* @__PURE__ */ React34.createElement(
13469
+ "div",
13470
+ {
13471
+ className: "editor-markdown-preview-content",
13472
+ dangerouslySetInnerHTML: { __html: previewHtml }
13473
+ }
13474
+ ))),
13475
+ /* @__PURE__ */ React34.createElement("div", { className: "editor-markdown-footer" }, /* @__PURE__ */ React34.createElement("div", { className: "editor-markdown-hint" }, /* @__PURE__ */ React34.createElement("kbd", null, "Ctrl+Enter"), " to save \u2022 ", /* @__PURE__ */ React34.createElement("kbd", null, "Esc"), " to cancel"), /* @__PURE__ */ React34.createElement("div", { className: "editor-markdown-actions" }, /* @__PURE__ */ React34.createElement(
13476
+ "button",
13477
+ {
13478
+ type: "button",
13479
+ className: "editor-btn editor-btn-secondary",
13480
+ onClick: onCancel
13481
+ },
13482
+ "Cancel"
13483
+ ), /* @__PURE__ */ React34.createElement(
13484
+ "button",
13485
+ {
13486
+ type: "button",
13487
+ className: "editor-btn editor-btn-primary",
13488
+ onClick: onCommit
13489
+ },
13490
+ "Save"
13491
+ )))
13492
+ );
13493
+ }
13494
+ MarkdownEditor.displayName = "MarkdownEditor";
13495
+ function renderMarkdownPreview(markdown) {
13496
+ if (!markdown) return '<p class="editor-markdown-empty">Nothing to preview</p>';
13497
+ let html = markdown;
13498
+ html = html.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
13499
+ html = html.replace(/^### (.*$)/gim, "<h3>$1</h3>");
13500
+ html = html.replace(/^## (.*$)/gim, "<h2>$1</h2>");
13501
+ html = html.replace(/^# (.*$)/gim, "<h1>$1</h1>");
13502
+ html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
13503
+ html = html.replace(/__(.+?)__/g, "<strong>$1</strong>");
13504
+ html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
13505
+ html = html.replace(/_(.+?)_/g, "<em>$1</em>");
13506
+ html = html.replace(/`(.+?)`/g, "<code>$1</code>");
13507
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
13508
+ html = html.replace(/^\* (.+)$/gim, "<li>$1</li>");
13509
+ html = html.replace(/^- (.+)$/gim, "<li>$1</li>");
13510
+ html = html.replace(/(<li>.*<\/li>)/s, "<ul>$1</ul>");
13511
+ html = html.replace(/\n\n/g, "</p><p>");
13512
+ html = html.replace(/\n/g, "<br>");
13513
+ if (!html.startsWith("<h") && !html.startsWith("<ul") && !html.startsWith("<p")) {
13514
+ html = "<p>" + html + "</p>";
13515
+ }
13516
+ return html;
13517
+ }
11755
13518
  export {
11756
13519
  AdvancedFilterBuilder,
11757
13520
  BadgeCell,
@@ -11760,6 +13523,7 @@ export {
11760
13523
  ColumnFilters,
11761
13524
  CurrencyCell,
11762
13525
  DataGrid,
13526
+ DateEditor,
11763
13527
  DensityToggle,
11764
13528
  ExportMenu,
11765
13529
  FacetedSearch,
@@ -11772,11 +13536,16 @@ export {
11772
13536
  LayoutPersistenceManager,
11773
13537
  LayoutPresetsManager,
11774
13538
  LocalStorageAdapter,
13539
+ MarkdownEditor,
11775
13540
  MarketDataEngine,
11776
13541
  MarketDataGrid,
13542
+ MultiSelectEditor,
13543
+ NumericEditor,
13544
+ PivotToolbar,
11777
13545
  PriorityIndicator,
11778
13546
  ProgressBar,
11779
13547
  Rating,
13548
+ RichSelectEditor,
11780
13549
  ServerAdapter,
11781
13550
  ServerSideDataSource,
11782
13551
  StatusChip,
@@ -11786,6 +13555,7 @@ export {
11786
13555
  VirtualScroller,
11787
13556
  WebSocketMockFeed,
11788
13557
  alpineTheme,
13558
+ buildPivot,
11789
13559
  buildTreeFromFlat,
11790
13560
  collapseAllNodes,
11791
13561
  countTreeNodes,
@@ -11795,12 +13565,17 @@ export {
11795
13565
  createMockWebSocket,
11796
13566
  createPreset,
11797
13567
  darkTheme,
13568
+ debounce2 as debounce,
11798
13569
  densityConfigs,
13570
+ downloadCSV,
11799
13571
  expandAllNodes,
13572
+ exportPivotToCSV,
11800
13573
  exportToCSV,
11801
13574
  exportToXLSX,
13575
+ filterOptions,
11802
13576
  filterTree,
11803
13577
  flattenTree,
13578
+ formatNumber,
11804
13579
  generateDensityCSS,
11805
13580
  generateFilename,
11806
13581
  generatePresetId,
@@ -11818,10 +13593,15 @@ export {
11818
13593
  isTreeNode,
11819
13594
  loadDensityMode,
11820
13595
  materialTheme,
13596
+ parseFormattedNumber,
11821
13597
  quartzTheme,
11822
13598
  saveDensityMode,
11823
13599
  themes,
11824
13600
  toggleNodeExpansion,
11825
13601
  useDensityMode,
11826
- useMarketData
13602
+ useEditorAutoFocus,
13603
+ useEditorClickOutside,
13604
+ useEditorKeyboardNavigation,
13605
+ useMarketData,
13606
+ usePopupPosition
11827
13607
  };