@iyulab/flex-table 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,13 @@
1
1
  import type { ColumnDefinition, DataRow } from '../models/types.js';
2
- export type ExportFormat = 'csv' | 'tsv' | 'json';
2
+ export type ExportFormat = 'csv' | 'tsv' | 'json' | 'xlsx';
3
3
  /**
4
4
  * Export data to the specified format.
5
+ * Returns string for text formats (csv/tsv/json) or Uint8Array for xlsx.
5
6
  */
6
- export declare function exportData(data: DataRow[], columns: ColumnDefinition[], format: ExportFormat): string;
7
+ export declare function exportData(data: DataRow[], columns: ColumnDefinition[], format: ExportFormat): string | Uint8Array;
7
8
  /**
8
9
  * Trigger a file download in the browser.
9
10
  */
10
- export declare function downloadFile(content: string, filename: string, mimeType: string): void;
11
+ export declare function downloadFile(content: string | Uint8Array, filename: string, mimeType: string): void;
11
12
  export declare function getExportMimeType(format: ExportFormat): string;
12
13
  export declare function getExportExtension(format: ExportFormat): string;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Minimal XLSX writer — no external dependencies.
3
+ * Generates OOXML (.xlsx) files using ZIP STORE method (no compression).
4
+ * Supports string, number, boolean, and date cell types.
5
+ */
6
+ import type { ColumnDefinition, DataRow } from '../models/types.js';
7
+ /**
8
+ * Build an XLSX file as a Uint8Array.
9
+ */
10
+ export declare function buildXlsx(data: DataRow[], columns: ColumnDefinition[]): Uint8Array;
@@ -519,6 +519,42 @@ var s = t`
519
519
  color: white;
520
520
  }
521
521
 
522
+ /* Body context menu */
523
+ .ft-body-context-menu {
524
+ background: var(--ft-editor-bg, #fff);
525
+ border: 1px solid var(--ft-border-color);
526
+ box-shadow: 0 2px 8px rgba(0,0,0,0.16);
527
+ min-width: 180px;
528
+ border-radius: 4px;
529
+ overflow: hidden;
530
+ padding: 4px 0;
531
+ }
532
+
533
+ .ft-context-menu-item {
534
+ padding: 6px 14px;
535
+ cursor: pointer;
536
+ font-size: var(--ft-font-size, 14px);
537
+ color: var(--ft-text-color);
538
+ white-space: nowrap;
539
+ user-select: none;
540
+ }
541
+
542
+ .ft-context-menu-item:hover {
543
+ background: var(--ft-selection-bg);
544
+ color: var(--ft-active-color);
545
+ }
546
+
547
+ .ft-context-menu-item.ft-context-menu-danger:hover {
548
+ background: #fdd;
549
+ color: #c0392b;
550
+ }
551
+
552
+ .ft-context-menu-separator {
553
+ height: 1px;
554
+ background: var(--ft-border-color);
555
+ margin: 4px 0;
556
+ }
557
+
522
558
  /* Find / Replace panel */
523
559
  .ft-find-panel {
524
560
  position: sticky;
@@ -866,13 +902,13 @@ function S(e, t, n) {
866
902
  if (c && l) continue;
867
903
  if (c) return 1;
868
904
  if (l) return -1;
869
- let u = C(t, s, i.get(a) ?? "text");
905
+ let u = ee(t, s, i.get(a) ?? "text");
870
906
  if (u !== 0) return o === "asc" ? u : -u;
871
907
  }
872
908
  return n - r;
873
909
  }), r;
874
910
  }
875
- function C(e, t, n) {
911
+ function ee(e, t, n) {
876
912
  switch (n) {
877
913
  case "number": {
878
914
  let n = Number(e), r = Number(t);
@@ -887,7 +923,7 @@ function C(e, t, n) {
887
923
  default: return String(e).localeCompare(String(t));
888
924
  }
889
925
  }
890
- function w(e, t, n) {
926
+ function C(e, t, n) {
891
927
  let r = e.find((e) => e.key === t);
892
928
  return n ? r ? r.direction === "asc" ? e.map((e) => e.key === t ? {
893
929
  ...e,
@@ -905,7 +941,7 @@ function w(e, t, n) {
905
941
  }
906
942
  //#endregion
907
943
  //#region src/core/filtering.ts
908
- function T(e, t, n) {
944
+ function w(e, t, n) {
909
945
  if (t.length === 0) return Array.from({ length: e.length }, (e, t) => t);
910
946
  let r = [];
911
947
  for (let i = 0; i < e.length; i++) {
@@ -924,9 +960,9 @@ function T(e, t, n) {
924
960
  }
925
961
  //#endregion
926
962
  //#region src/core/undo.ts
927
- var E = 100, D = class {
963
+ var T = 100, E = class {
928
964
  constructor() {
929
- this._undoStack = [], this._redoStack = [], this._maxSize = E;
965
+ this._undoStack = [], this._redoStack = [], this._maxSize = T;
930
966
  }
931
967
  get maxSize() {
932
968
  return this._maxSize;
@@ -963,7 +999,7 @@ var E = 100, D = class {
963
999
  };
964
1000
  //#endregion
965
1001
  //#region src/clipboard/clipboard.ts
966
- function O(e, t, n) {
1002
+ function te(e, t, n) {
967
1003
  let r = [];
968
1004
  for (let i = n.startRow; i <= n.endRow; i++) {
969
1005
  let a = e[i], o = [];
@@ -975,7 +1011,7 @@ function O(e, t, n) {
975
1011
  }
976
1012
  return r.join("\n");
977
1013
  }
978
- function k(e) {
1014
+ function ne(e) {
979
1015
  let t = e.replace(/\r\n/g, "\n").replace(/\r/g, "\n"), n = [], r = [], i = 0, a = t.length;
980
1016
  for (; i <= a;) {
981
1017
  if (i === a) {
@@ -1004,7 +1040,7 @@ function k(e) {
1004
1040
  }
1005
1041
  return n;
1006
1042
  }
1007
- function A(e, t) {
1043
+ function D(e, t) {
1008
1044
  if (e === "") return null;
1009
1045
  switch (t.type) {
1010
1046
  case "number": {
@@ -1016,24 +1052,122 @@ function A(e, t) {
1016
1052
  }
1017
1053
  }
1018
1054
  //#endregion
1055
+ //#region src/export/xlsx-writer.ts
1056
+ var re = (() => {
1057
+ let e = new Uint32Array(256);
1058
+ for (let t = 0; t < 256; t++) {
1059
+ let n = t;
1060
+ for (let e = 0; e < 8; e++) n = n & 1 ? 3988292384 ^ n >>> 1 : n >>> 1;
1061
+ e[t] = n;
1062
+ }
1063
+ return e;
1064
+ })();
1065
+ function O(e) {
1066
+ let t = 4294967295;
1067
+ for (let n = 0; n < e.length; n++) t = re[(t ^ e[n]) & 255] ^ t >>> 8;
1068
+ return (t ^ 4294967295) >>> 0;
1069
+ }
1070
+ var k = new TextEncoder();
1071
+ function A(e) {
1072
+ let t = [], n = [], r = 0;
1073
+ for (let i of e) {
1074
+ let e = k.encode(i.name), a = O(i.data), o = i.data.length, s = new ArrayBuffer(30 + e.length), c = new DataView(s);
1075
+ c.setUint32(0, 67324752, !0), c.setUint16(4, 20, !0), c.setUint16(6, 0, !0), c.setUint16(8, 0, !0), c.setUint16(10, 0, !0), c.setUint16(12, 33, !0), c.setUint32(14, a, !0), c.setUint32(18, o, !0), c.setUint32(22, o, !0), c.setUint16(26, e.length, !0), c.setUint16(28, 0, !0), new Uint8Array(s, 30).set(e), t.push(new Uint8Array(s)), t.push(i.data);
1076
+ let l = new ArrayBuffer(46 + e.length), u = new DataView(l);
1077
+ u.setUint32(0, 33639248, !0), u.setUint16(4, 20, !0), u.setUint16(6, 20, !0), u.setUint16(8, 0, !0), u.setUint16(10, 0, !0), u.setUint16(12, 0, !0), u.setUint16(14, 33, !0), u.setUint32(16, a, !0), u.setUint32(20, o, !0), u.setUint32(24, o, !0), u.setUint16(28, e.length, !0), u.setUint16(30, 0, !0), u.setUint16(32, 0, !0), u.setUint16(34, 0, !0), u.setUint16(36, 0, !0), u.setUint32(38, 0, !0), u.setUint32(42, r, !0), new Uint8Array(l, 46).set(e), n.push(new Uint8Array(l)), r += 30 + e.length + o;
1078
+ }
1079
+ let i = j(n), a = /* @__PURE__ */ new ArrayBuffer(22), o = new DataView(a);
1080
+ return o.setUint32(0, 101010256, !0), o.setUint16(4, 0, !0), o.setUint16(6, 0, !0), o.setUint16(8, e.length, !0), o.setUint16(10, e.length, !0), o.setUint32(12, i.length, !0), o.setUint32(16, r, !0), o.setUint16(20, 0, !0), j([
1081
+ ...t,
1082
+ i,
1083
+ new Uint8Array(a)
1084
+ ]);
1085
+ }
1086
+ function j(e) {
1087
+ let t = e.reduce((e, t) => e + t.length, 0), n = new Uint8Array(t), r = 0;
1088
+ for (let t of e) n.set(t, r), r += t.length;
1089
+ return n;
1090
+ }
1091
+ function M(e) {
1092
+ return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1093
+ }
1094
+ function N(e) {
1095
+ let t = "";
1096
+ for (e++; e > 0;) t = String.fromCharCode(64 + (e % 26 || 26)) + t, e = Math.floor((e - 1) / 26);
1097
+ return t;
1098
+ }
1099
+ function P(e, t) {
1100
+ return N(t) + (e + 1);
1101
+ }
1102
+ function F(e) {
1103
+ let t = Date.UTC(1899, 11, 30);
1104
+ return (e.getTime() - t) / 864e5;
1105
+ }
1106
+ var I = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">\n <Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n <Default Extension=\"xml\" ContentType=\"application/xml\"/>\n <Override PartName=\"/xl/workbook.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml\"/>\n <Override PartName=\"/xl/worksheets/sheet1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml\"/>\n <Override PartName=\"/xl/styles.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml\"/>\n</Types>", L = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n <Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\" Target=\"xl/workbook.xml\"/>\n</Relationships>", R = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<workbook xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">\n <sheets><sheet name=\"Sheet1\" sheetId=\"1\" r:id=\"rId1\"/></sheets>\n</workbook>", z = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n <Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet\" Target=\"worksheets/sheet1.xml\"/>\n <Relationship Id=\"rId2\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles\" Target=\"styles.xml\"/>\n</Relationships>", B = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<styleSheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">\n <numFmts count=\"1\"><numFmt numFmtId=\"164\" formatCode=\"yyyy-mm-dd\"/></numFmts>\n <fonts count=\"2\">\n <font><sz val=\"11\"/><name val=\"Calibri\"/></font>\n <font><sz val=\"11\"/><b/><name val=\"Calibri\"/></font>\n </fonts>\n <fills count=\"2\">\n <fill><patternFill patternType=\"none\"/></fill>\n <fill><patternFill patternType=\"gray125\"/></fill>\n </fills>\n <borders count=\"1\"><border><left/><right/><top/><bottom/><diagonal/></border></borders>\n <cellStyleXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\"/></cellStyleXfs>\n <cellXfs count=\"3\">\n <xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\" xfId=\"0\"/>\n <xf numFmtId=\"164\" fontId=\"0\" fillId=\"0\" borderId=\"0\" xfId=\"0\" applyNumberFormat=\"1\"/>\n <xf numFmtId=\"0\" fontId=\"1\" fillId=\"0\" borderId=\"0\" xfId=\"0\" applyFont=\"1\"/>\n </cellXfs>\n</styleSheet>";
1107
+ function V(e, t) {
1108
+ let n = [], r = t.map((e, t) => `<c r="${P(0, t)}" t="str" s="2"><v>${M(e.header)}</v></c>`);
1109
+ return n.push(`<row r="1">${r.join("")}</row>`), e.forEach((e, r) => {
1110
+ let i = t.map((t, n) => {
1111
+ let i = P(r + 1, n), a = e[t.key];
1112
+ if (a == null) return `<c r="${i}" t="str"><v></v></c>`;
1113
+ let o = t.type ?? "text";
1114
+ return o === "number" && typeof a == "number" ? `<c r="${i}"><v>${a}</v></c>` : (o === "date" || o === "datetime") && a instanceof Date ? `<c r="${i}" s="1"><v>${F(a).toFixed(6)}</v></c>` : o === "boolean" ? `<c r="${i}" t="b"><v>${+!!a}</v></c>` : `<c r="${i}" t="str"><v>${M(String(a))}</v></c>`;
1115
+ });
1116
+ n.push(`<row r="${r + 2}">${i.join("")}</row>`);
1117
+ }), `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1118
+ <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
1119
+ <sheetData>${n.join("")}</sheetData>
1120
+ </worksheet>`;
1121
+ }
1122
+ function H(e, t) {
1123
+ let n = V(e, t);
1124
+ return A([
1125
+ {
1126
+ name: "[Content_Types].xml",
1127
+ data: k.encode(I)
1128
+ },
1129
+ {
1130
+ name: "_rels/.rels",
1131
+ data: k.encode(L)
1132
+ },
1133
+ {
1134
+ name: "xl/workbook.xml",
1135
+ data: k.encode(R)
1136
+ },
1137
+ {
1138
+ name: "xl/_rels/workbook.xml.rels",
1139
+ data: k.encode(z)
1140
+ },
1141
+ {
1142
+ name: "xl/worksheets/sheet1.xml",
1143
+ data: k.encode(n)
1144
+ },
1145
+ {
1146
+ name: "xl/styles.xml",
1147
+ data: k.encode(B)
1148
+ }
1149
+ ]);
1150
+ }
1151
+ //#endregion
1019
1152
  //#region src/export/export.ts
1020
- function j(e, t, n) {
1153
+ function U(e, t, n) {
1021
1154
  switch (n) {
1022
- case "csv": return N(e, t, ",");
1023
- case "tsv": return N(e, t, " ");
1024
- case "json": return F(e, t);
1155
+ case "csv": return G(e, t, ",");
1156
+ case "tsv": return G(e, t, " ");
1157
+ case "json": return q(e, t);
1158
+ case "xlsx": return H(e, t);
1025
1159
  }
1026
1160
  }
1027
- function M(e, t) {
1161
+ function W(e, t) {
1028
1162
  return e == null ? "" : (t.type === "date" || t.type === "datetime") && e instanceof Date ? e.toISOString() : String(e);
1029
1163
  }
1030
- function N(e, t, n) {
1031
- return [t.map((e) => P(e.header, n)).join(n), ...e.map((e) => t.map((t) => P(M(e[t.key], t), n)).join(n))].join("\n");
1164
+ function G(e, t, n) {
1165
+ return [t.map((e) => K(e.header, n)).join(n), ...e.map((e) => t.map((t) => K(W(e[t.key], t), n)).join(n))].join("\n");
1032
1166
  }
1033
- function P(e, t) {
1167
+ function K(e, t) {
1034
1168
  return e.includes(t) || e.includes("\"") || e.includes("\n") || e.includes("\r") ? "\"" + e.replace(/"/g, "\"\"") + "\"" : e;
1035
1169
  }
1036
- function F(e, t) {
1170
+ function q(e, t) {
1037
1171
  let n = e.map((e) => {
1038
1172
  let n = {};
1039
1173
  for (let r of t) {
@@ -1044,28 +1178,30 @@ function F(e, t) {
1044
1178
  });
1045
1179
  return JSON.stringify(n, null, 2);
1046
1180
  }
1047
- function I(e, t, n) {
1181
+ function J(e, t, n) {
1048
1182
  let r = new Blob([e], { type: n }), i = URL.createObjectURL(r), a = document.createElement("a");
1049
1183
  a.href = i, a.download = t, a.style.display = "none", document.body.appendChild(a), a.click(), document.body.removeChild(a), setTimeout(() => URL.revokeObjectURL(i), 100);
1050
1184
  }
1051
- var L = {
1185
+ var ie = {
1052
1186
  csv: "text/csv;charset=utf-8",
1053
1187
  tsv: "text/tab-separated-values;charset=utf-8",
1054
- json: "application/json;charset=utf-8"
1055
- }, R = {
1188
+ json: "application/json;charset=utf-8",
1189
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
1190
+ }, ae = {
1056
1191
  csv: ".csv",
1057
1192
  tsv: ".tsv",
1058
- json: ".json"
1193
+ json: ".json",
1194
+ xlsx: ".xlsx"
1059
1195
  };
1060
- function z(e) {
1061
- return L[e];
1196
+ function oe(e) {
1197
+ return ie[e];
1062
1198
  }
1063
- function B(e) {
1064
- return R[e];
1199
+ function se(e) {
1200
+ return ae[e];
1065
1201
  }
1066
1202
  //#endregion
1067
1203
  //#region \0@oxc-project+runtime@0.130.0/helpers/decorate.js
1068
- function V(e, t, n, r) {
1204
+ function Y(e, t, n, r) {
1069
1205
  var i = arguments.length, a = i < 3 ? t : r === null ? r = Object.getOwnPropertyDescriptor(t, n) : r, o;
1070
1206
  if (typeof Reflect == "object" && typeof Reflect.decorate == "function") a = Reflect.decorate(e, t, n, r);
1071
1207
  else for (var s = e.length - 1; s >= 0; s--) (o = e[s]) && (a = (i < 3 ? o(a) : i > 3 ? o(t, n, a) : o(t, n)) || a);
@@ -1073,9 +1209,9 @@ function V(e, t, n, r) {
1073
1209
  }
1074
1210
  //#endregion
1075
1211
  //#region src/flex-table.ts
1076
- var H = 120, U = 40, W = 32, G = 5, K = class extends e {
1212
+ var X = 120, Z = 40, ce = 32, Q = 5, $ = class extends e {
1077
1213
  constructor(...e) {
1078
- super(...e), this.columns = [], this.data = [], this.rowHeight = W, this.showRowNumbers = !1, this.theme = void 0, this.maxRows = 0, this.editable = !0, this.showFilters = !1, this.selectable = !1, this.dataMode = "client", this.footerData = null, this._scrollTop = 0, this._scrollLeft = 0, this._viewportHeight = 0, this._viewportWidth = 0, this._colLeftOffsets = [], this._totalRowWidth = 0, this._activeCell = null, this._editingCell = null, this._sortCriteria = [], this._selection = new y(), this._editing = new b(), this._rowSelection = new x(), this._undo = new D(), this._filters = [], this._filteredIndices = [], this._sortedIndices = [], this._openFilterKey = null, this._rowSelectionVersion = 0, this._autocompleteState = null, this._headerMenu = null, this._viewDirty = !0, this._isDragging = !1, this._wasDrag = !1, this._resizing = null, this._resizeCleanup = null, this._colDrag = null, this._colDragIndicatorLeft = null, this._fillDrag = null, this._rowDrag = null, this._rowDragIndicatorY = null, this._findState = null, this._columnWidths = /* @__PURE__ */ new Map(), this._hostResizeObserver = null, this._invalidCells = /* @__PURE__ */ new Map(), this._textFilterState = /* @__PURE__ */ new Map(), this._numberFilterState = /* @__PURE__ */ new Map(), this._dateFilterState = /* @__PURE__ */ new Map();
1214
+ super(...e), this.columns = [], this.data = [], this.rowHeight = ce, this.showRowNumbers = !1, this.theme = void 0, this.maxRows = 0, this.editable = !0, this.showFilters = !1, this.showContextMenu = !1, this.selectable = !1, this.dataMode = "client", this.footerData = null, this._scrollTop = 0, this._scrollLeft = 0, this._viewportHeight = 0, this._viewportWidth = 0, this._colLeftOffsets = [], this._totalRowWidth = 0, this._activeCell = null, this._editingCell = null, this._sortCriteria = [], this._selection = new y(), this._editing = new b(), this._rowSelection = new x(), this._undo = new E(), this._filters = [], this._filteredIndices = [], this._sortedIndices = [], this._openFilterKey = null, this._rowSelectionVersion = 0, this._autocompleteState = null, this._headerMenu = null, this._bodyContextMenu = null, this._viewDirty = !0, this._isDragging = !1, this._wasDrag = !1, this._resizing = null, this._resizeCleanup = null, this._colDrag = null, this._colDragIndicatorLeft = null, this._fillDrag = null, this._rowDrag = null, this._rowDragIndicatorY = null, this._findState = null, this._columnWidths = /* @__PURE__ */ new Map(), this._hostResizeObserver = null, this._invalidCells = /* @__PURE__ */ new Map(), this._textFilterState = /* @__PURE__ */ new Map(), this._numberFilterState = /* @__PURE__ */ new Map(), this._dateFilterState = /* @__PURE__ */ new Map();
1079
1215
  }
1080
1216
  static {
1081
1217
  this.styles = s;
@@ -1384,12 +1520,12 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
1384
1520
  if (!t) return "";
1385
1521
  let n = this.visibleColumns.slice(t.startCol, t.endCol + 1), r = [];
1386
1522
  for (let e = t.startRow; e <= t.endRow; e++) r.push(this.data[this._toDataIndex(e)]);
1387
- return j(r, n, e);
1523
+ return U(r, n, e);
1388
1524
  }
1389
- return j(this._sortedIndices.map((e) => this.data[e]), this.visibleColumns, e);
1525
+ return U(this._sortedIndices.map((e) => this.data[e]), this.visibleColumns, e);
1390
1526
  }
1391
1527
  exportToFile(e, t) {
1392
- I(this.exportToString(e), t ?? `export${B(e)}`, z(e));
1528
+ J(this.exportToString(e), t ?? `export${se(e)}`, oe(e));
1393
1529
  }
1394
1530
  _getSelectedDataRows() {
1395
1531
  let e = this._selection.getEffectiveRange();
@@ -1402,8 +1538,8 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
1402
1538
  return this._columnWidths.get(e);
1403
1539
  }
1404
1540
  _getColWidth(e) {
1405
- let t = this._columnWidths.get(e.key) ?? e.width ?? H;
1406
- return Math.max(t, e.minWidth ?? U);
1541
+ let t = this._columnWidths.get(e.key) ?? e.width ?? X;
1542
+ return Math.max(t, e.minWidth ?? Z);
1407
1543
  }
1408
1544
  get headerHeight() {
1409
1545
  return this.rowHeight + 8;
@@ -1415,10 +1551,10 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
1415
1551
  return this._visibleRowCount * this.rowHeight;
1416
1552
  }
1417
1553
  get visibleRange() {
1418
- let e = Math.max(0, this._scrollTop - this.headerHeight), t = Math.max(0, Math.floor(e / this.rowHeight) - G), n = Math.ceil(this._viewportHeight / this.rowHeight);
1554
+ let e = Math.max(0, this._scrollTop - this.headerHeight), t = Math.max(0, Math.floor(e / this.rowHeight) - Q), n = Math.ceil(this._viewportHeight / this.rowHeight);
1419
1555
  return {
1420
1556
  start: t,
1421
- end: Math.min(this._visibleRowCount, Math.floor(e / this.rowHeight) + n + G)
1557
+ end: Math.min(this._visibleRowCount, Math.floor(e / this.rowHeight) + n + Q)
1422
1558
  };
1423
1559
  }
1424
1560
  connectedCallback() {
@@ -1451,13 +1587,13 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
1451
1587
  i = r;
1452
1588
  break;
1453
1589
  }
1454
- i = Math.max(0, i - G);
1590
+ i = Math.max(0, i - Q);
1455
1591
  let a = e.length;
1456
1592
  for (let e = i; e < t.length; e++) if (t[e] > r) {
1457
1593
  a = e;
1458
1594
  break;
1459
1595
  }
1460
- return a = Math.min(e.length, a + G), {
1596
+ return a = Math.min(e.length, a + Q), {
1461
1597
  start: i,
1462
1598
  end: a
1463
1599
  };
@@ -1473,7 +1609,7 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
1473
1609
  this._filteredIndices = Array.from({ length: this.data.length }, (e, t) => t), this._sortedIndices = this._filteredIndices;
1474
1610
  return;
1475
1611
  }
1476
- if (this._filteredIndices = T(this.data, this._filters, (e, t, n) => {
1612
+ if (this._filteredIndices = w(this.data, this._filters, (e, t, n) => {
1477
1613
  this.dispatchEvent(new CustomEvent("filter-error", {
1478
1614
  detail: {
1479
1615
  error: e,
@@ -1506,17 +1642,19 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
1506
1642
  this._scrollTop = this.scrollTop, this._scrollLeft = this.scrollLeft;
1507
1643
  }
1508
1644
  _onDocumentClick() {
1509
- this._openFilterKey &&= null, this._headerMenu &&= null;
1645
+ this._openFilterKey &&= null, this._headerMenu &&= null, this._bodyContextMenu &&= null;
1510
1646
  }
1511
1647
  _onContextMenu(e) {
1512
1648
  let t = e.composedPath().find((e) => e instanceof HTMLElement && e.classList.contains("ft-cell"));
1513
1649
  if (!t) return;
1650
+ e.preventDefault();
1514
1651
  let n = t.parentElement;
1515
1652
  if (!n || !n.classList.contains("ft-row")) return;
1516
1653
  let r = parseInt(t.dataset.colIndex ?? "-1", 10), i = n.style.top, a = Math.round(parseInt(i, 10) / this.rowHeight);
1517
1654
  if (r < 0 || a < 0) return;
1518
1655
  let o = this._toDataIndex(a), s = this.visibleColumns[r];
1519
- s && this.dispatchEvent(new CustomEvent("context-menu", {
1656
+ if (!s) return;
1657
+ let c = new CustomEvent("context-menu", {
1520
1658
  detail: {
1521
1659
  x: e.clientX,
1522
1660
  y: e.clientY,
@@ -1527,7 +1665,17 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
1527
1665
  rowData: this.data[o]
1528
1666
  },
1529
1667
  bubbles: !0,
1530
- composed: !0
1668
+ composed: !0,
1669
+ cancelable: !0
1670
+ });
1671
+ this.dispatchEvent(c), this.showContextMenu && !c.defaultPrevented && (this._bodyContextMenu = {
1672
+ rowIndex: a,
1673
+ colIndex: r,
1674
+ dataIndex: o,
1675
+ x: e.clientX,
1676
+ y: e.clientY
1677
+ }, requestAnimationFrame(() => {
1678
+ document.addEventListener("click", this._onDocumentClick, { once: !0 });
1531
1679
  }));
1532
1680
  }
1533
1681
  _onCellClickEvent(e, t, n) {
@@ -1600,7 +1748,7 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
1600
1748
  }
1601
1749
  let t = this.shadowRoot?.querySelector(".ft-editor");
1602
1750
  if (t) {
1603
- let n = A(t.value, e);
1751
+ let n = D(t.value, e);
1604
1752
  this._applyEdit(n);
1605
1753
  } else this._cancelEdit();
1606
1754
  }
@@ -1793,7 +1941,7 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
1793
1941
  e.preventDefault();
1794
1942
  let n = t[this._activeCell.col];
1795
1943
  if (!n) return !0;
1796
- let r = this._columnWidths.get(n.key) ?? n.width ?? H, i = e.key === "ArrowRight" ? 20 : -20, a = n.minWidth ?? U, o = Math.max(a, r + i);
1944
+ let r = this._columnWidths.get(n.key) ?? n.width ?? X, i = e.key === "ArrowRight" ? 20 : -20, a = n.minWidth ?? Z, o = Math.max(a, r + i);
1797
1945
  return this._columnWidths.set(n.key, o), this.requestUpdate(), this.dispatchEvent(new CustomEvent("column-resize", {
1798
1946
  detail: {
1799
1947
  key: n.key,
@@ -1822,7 +1970,7 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
1822
1970
  async _handleCopy(e) {
1823
1971
  let t = this._selection.getEffectiveRange();
1824
1972
  if (!t) return;
1825
- let n = this.visibleColumns, r = O(this._sortedIndices.map((e) => this.data[e]), n, t);
1973
+ let n = this.visibleColumns, r = te(this._sortedIndices.map((e) => this.data[e]), n, t);
1826
1974
  try {
1827
1975
  await navigator.clipboard.writeText(r);
1828
1976
  } catch (e) {
@@ -1848,7 +1996,7 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
1848
1996
  if (!this._activeCell) return;
1849
1997
  let e = await this._readClipboardText();
1850
1998
  if (e == null) return;
1851
- let t = k(e);
1999
+ let t = ne(e);
1852
2000
  if (t.length === 0) return;
1853
2001
  let n = this._expandRowsForPaste(this._activeCell.row, t.length), r = this._applyPasteData(this._activeCell, t);
1854
2002
  if (r.length > 0 || n.length > 0) {
@@ -1906,7 +2054,7 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
1906
2054
  for (let a = 0; a < t[i].length; a++) {
1907
2055
  let s = e.col + a;
1908
2056
  if (s >= n.length) break;
1909
- let c = n[s], l = this.data[o][c.key], u = A(t[i][a], c);
2057
+ let c = n[s], l = this.data[o][c.key], u = D(t[i][a], c);
1910
2058
  this.data[o][c.key] = u, r.push({
1911
2059
  row: o,
1912
2060
  col: s,
@@ -1971,7 +2119,7 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
1971
2119
  for (let r = e.startCol + 1; r <= e.endCol; r++) {
1972
2120
  let e = t[r];
1973
2121
  if (!e || !this._isCellEditable(e)) continue;
1974
- let a = A(o == null ? "" : String(o), e);
2122
+ let a = D(o == null ? "" : String(o), e);
1975
2123
  n.push({
1976
2124
  row: i,
1977
2125
  key: e.key,
@@ -2062,7 +2210,7 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
2062
2210
  this._selectColumn(n);
2063
2211
  return;
2064
2212
  }
2065
- t.sortable !== !1 && (this._sortCriteria = w(this._sortCriteria, t.key, e.shiftKey), this.dataMode !== "server" && this._recomputeView(), this.requestUpdate(), this.dispatchEvent(new CustomEvent("sort-change", {
2213
+ t.sortable !== !1 && (this._sortCriteria = C(this._sortCriteria, t.key, e.shiftKey), this.dataMode !== "server" && this._recomputeView(), this.requestUpdate(), this.dispatchEvent(new CustomEvent("sort-change", {
2066
2214
  detail: { criteria: [...this._sortCriteria] },
2067
2215
  bubbles: !0,
2068
2216
  composed: !0
@@ -2194,6 +2342,89 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
2194
2342
  </div>
2195
2343
  ` : r;
2196
2344
  }
2345
+ _renderBodyContextMenu() {
2346
+ if (!this._bodyContextMenu) return r;
2347
+ let { colIndex: e, dataIndex: t, x: i, y: a } = this._bodyContextMenu, o = this.visibleColumns[e];
2348
+ if (!o) return r;
2349
+ let s = this.data[t]?.[o.key], c = this._filters.some((e) => e.key === o.key), l = () => {
2350
+ this._bodyContextMenu = null;
2351
+ };
2352
+ return n`
2353
+ <div class="ft-body-context-menu" style="position: fixed; left: ${i + 200 > window.innerWidth ? i - 200 : i}px; top: ${a + 280 > window.innerHeight ? a - 280 : a}px; z-index: 200;"
2354
+ @mousedown=${(e) => e.stopPropagation()}>
2355
+ <div class="ft-context-menu-item"
2356
+ @click=${() => {
2357
+ this._handleCopy(!1), l();
2358
+ }}>
2359
+ Copy
2360
+ </div>
2361
+ <div class="ft-context-menu-separator"></div>
2362
+ <div class="ft-context-menu-item"
2363
+ @click=${() => {
2364
+ this.addRow(void 0, t), l();
2365
+ }}>
2366
+ Insert row above
2367
+ </div>
2368
+ <div class="ft-context-menu-item"
2369
+ @click=${() => {
2370
+ this.addRow(void 0, t + 1), l();
2371
+ }}>
2372
+ Insert row below
2373
+ </div>
2374
+ <div class="ft-context-menu-item ft-context-menu-danger"
2375
+ @click=${() => {
2376
+ this.deleteRows([t]), l();
2377
+ }}>
2378
+ Delete row
2379
+ </div>
2380
+ <div class="ft-context-menu-separator"></div>
2381
+ <div class="ft-context-menu-item"
2382
+ @click=${() => {
2383
+ this._setColumnHidden(o.key, !0), l();
2384
+ }}>
2385
+ Hide column
2386
+ </div>
2387
+ <div class="ft-context-menu-separator"></div>
2388
+ <div class="ft-context-menu-item"
2389
+ @click=${() => {
2390
+ this._applySortFromMenu(o.key, "asc"), l();
2391
+ }}>
2392
+ Sort ascending ↑
2393
+ </div>
2394
+ <div class="ft-context-menu-item"
2395
+ @click=${() => {
2396
+ this._applySortFromMenu(o.key, "desc"), l();
2397
+ }}>
2398
+ Sort descending ↓
2399
+ </div>
2400
+ <div class="ft-context-menu-separator"></div>
2401
+ <div class="ft-context-menu-item"
2402
+ @click=${() => {
2403
+ this.setFilter(o.key, (e) => e === s), l();
2404
+ }}>
2405
+ Filter by this value
2406
+ </div>
2407
+ ${c ? n`
2408
+ <div class="ft-context-menu-item"
2409
+ @click=${() => {
2410
+ this.removeFilter(o.key), l();
2411
+ }}>
2412
+ Clear filter
2413
+ </div>
2414
+ ` : ""}
2415
+ </div>
2416
+ `;
2417
+ }
2418
+ _applySortFromMenu(e, t) {
2419
+ this.columns.find((t) => t.key === e) && (this._sortCriteria = [{
2420
+ key: e,
2421
+ direction: t
2422
+ }], this._sortedIndices = S(this.data, this._sortCriteria, this.columns), this.dispatchEvent(new CustomEvent("sort-change", {
2423
+ detail: { sortCriteria: [...this._sortCriteria] },
2424
+ bubbles: !0,
2425
+ composed: !0
2426
+ })), this.requestUpdate());
2427
+ }
2197
2428
  _adjustFilterDropdown() {
2198
2429
  if (!this._openFilterKey) return;
2199
2430
  let e = this.shadowRoot?.querySelector(".ft-filter-dropdown");
@@ -2334,7 +2565,7 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
2334
2565
  if (!n) return;
2335
2566
  let r = this.shadowRoot?.querySelectorAll(`.ft-cell[data-col-index="${t}"]`), i = 0, a = this.shadowRoot?.querySelector(`.ft-header-cell[data-col-index="${t}"]`);
2336
2567
  if (a && (i = Math.max(i, a.scrollWidth)), r) for (let e of r) i = Math.max(i, e.scrollWidth);
2337
- let o = n.minWidth ?? U, s = Math.max(o, i + 8);
2568
+ let o = n.minWidth ?? Z, s = Math.max(o, i + 8);
2338
2569
  this._columnWidths.set(n.key, s), this.requestUpdate(), this.dispatchEvent(new CustomEvent("column-resize", {
2339
2570
  detail: {
2340
2571
  key: n.key,
@@ -2347,7 +2578,7 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
2347
2578
  }
2348
2579
  _onResizeStart(e, t) {
2349
2580
  e.preventDefault(), e.stopPropagation();
2350
- let n = this.visibleColumns[t], r = this._columnWidths.get(n.key) ?? n.width ?? H;
2581
+ let n = this.visibleColumns[t], r = this._columnWidths.get(n.key) ?? n.width ?? X;
2351
2582
  this._resizing = {
2352
2583
  colIndex: t,
2353
2584
  startX: e.clientX,
@@ -2355,13 +2586,13 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
2355
2586
  };
2356
2587
  let i = (e) => {
2357
2588
  if (!this._resizing) return;
2358
- let t = e.clientX - this._resizing.startX, r = n.minWidth ?? U, i = Math.max(r, this._resizing.startWidth + t);
2589
+ let t = e.clientX - this._resizing.startX, r = n.minWidth ?? Z, i = Math.max(r, this._resizing.startWidth + t);
2359
2590
  this._columnWidths.set(n.key, i), this.requestUpdate();
2360
2591
  }, a = () => {
2361
2592
  document.removeEventListener("mousemove", i), document.removeEventListener("mouseup", o), this._resizeCleanup = null;
2362
2593
  }, o = () => {
2363
2594
  if (a(), this._resizing) {
2364
- let e = this._columnWidths.get(n.key) ?? H;
2595
+ let e = this._columnWidths.get(n.key) ?? X;
2365
2596
  this.dispatchEvent(new CustomEvent("column-resize", {
2366
2597
  detail: {
2367
2598
  key: n.key,
@@ -2812,6 +3043,7 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
2812
3043
  ${_}
2813
3044
  ${g}
2814
3045
  ${this._renderHeaderContextMenu()}
3046
+ ${this._renderBodyContextMenu()}
2815
3047
  `;
2816
3048
  }
2817
3049
  _onSelectAllChange(e) {
@@ -2991,36 +3223,39 @@ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
2991
3223
  return isNaN(t.getTime()) ? "" : `${t.getFullYear()}-${String(t.getMonth() + 1).padStart(2, "0")}-${String(t.getDate()).padStart(2, "0")}T${String(t.getHours()).padStart(2, "0")}:${String(t.getMinutes()).padStart(2, "0")}`;
2992
3224
  }
2993
3225
  };
2994
- V([a({ type: Array })], K.prototype, "columns", void 0), V([a({
3226
+ Y([a({ type: Array })], $.prototype, "columns", void 0), Y([a({
2995
3227
  type: Array,
2996
3228
  hasChanged: () => !0
2997
- })], K.prototype, "data", void 0), V([a({
3229
+ })], $.prototype, "data", void 0), Y([a({
2998
3230
  type: Number,
2999
3231
  attribute: "row-height"
3000
- })], K.prototype, "rowHeight", void 0), V([a({
3232
+ })], $.prototype, "rowHeight", void 0), Y([a({
3001
3233
  type: Boolean,
3002
3234
  attribute: "show-row-numbers"
3003
- })], K.prototype, "showRowNumbers", void 0), V([a({
3235
+ })], $.prototype, "showRowNumbers", void 0), Y([a({
3004
3236
  type: String,
3005
3237
  reflect: !0
3006
- })], K.prototype, "theme", void 0), V([a({
3238
+ })], $.prototype, "theme", void 0), Y([a({
3007
3239
  type: Number,
3008
3240
  attribute: "max-rows"
3009
- })], K.prototype, "maxRows", void 0), V([a({ type: Boolean })], K.prototype, "editable", void 0), V([a({
3241
+ })], $.prototype, "maxRows", void 0), Y([a({ type: Boolean })], $.prototype, "editable", void 0), Y([a({
3010
3242
  type: Boolean,
3011
3243
  attribute: "show-filters"
3012
- })], K.prototype, "showFilters", void 0), V([a({ type: Boolean })], K.prototype, "selectable", void 0), V([a({
3244
+ })], $.prototype, "showFilters", void 0), Y([a({
3245
+ type: Boolean,
3246
+ attribute: "show-context-menu"
3247
+ })], $.prototype, "showContextMenu", void 0), Y([a({ type: Boolean })], $.prototype, "selectable", void 0), Y([a({
3013
3248
  type: String,
3014
3249
  attribute: "selection-mode"
3015
- })], K.prototype, "selectionMode", null), V([a({
3250
+ })], $.prototype, "selectionMode", null), Y([a({
3016
3251
  type: String,
3017
3252
  attribute: "data-mode"
3018
- })], K.prototype, "dataMode", void 0), V([a({
3253
+ })], $.prototype, "dataMode", void 0), Y([a({
3019
3254
  type: Object,
3020
3255
  attribute: "footer-data"
3021
- })], K.prototype, "footerData", void 0), V([a({
3256
+ })], $.prototype, "footerData", void 0), Y([a({
3022
3257
  type: Number,
3023
3258
  attribute: "max-undo-size"
3024
- })], K.prototype, "maxUndoSize", null), V([o()], K.prototype, "_scrollTop", void 0), V([o()], K.prototype, "_scrollLeft", void 0), V([o()], K.prototype, "_viewportHeight", void 0), V([o()], K.prototype, "_viewportWidth", void 0), V([o()], K.prototype, "_activeCell", void 0), V([o()], K.prototype, "_editingCell", void 0), V([o()], K.prototype, "_sortCriteria", void 0), V([o()], K.prototype, "_openFilterKey", void 0), V([o()], K.prototype, "_rowSelectionVersion", void 0), V([o()], K.prototype, "_autocompleteState", void 0), V([o()], K.prototype, "_headerMenu", void 0), K = V([i("flex-table")], K);
3259
+ })], $.prototype, "maxUndoSize", null), Y([o()], $.prototype, "_scrollTop", void 0), Y([o()], $.prototype, "_scrollLeft", void 0), Y([o()], $.prototype, "_viewportHeight", void 0), Y([o()], $.prototype, "_viewportWidth", void 0), Y([o()], $.prototype, "_activeCell", void 0), Y([o()], $.prototype, "_editingCell", void 0), Y([o()], $.prototype, "_sortCriteria", void 0), Y([o()], $.prototype, "_openFilterKey", void 0), Y([o()], $.prototype, "_rowSelectionVersion", void 0), Y([o()], $.prototype, "_autocompleteState", void 0), Y([o()], $.prototype, "_headerMenu", void 0), Y([o()], $.prototype, "_bodyContextMenu", void 0), $ = Y([i("flex-table")], $);
3025
3260
  //#endregion
3026
- export { f as a, x as i, j as n, D as r, K as t };
3261
+ export { f as a, x as i, U as n, E as r, $ as t };
@@ -15,6 +15,8 @@ export declare class FlexTable extends LitElement {
15
15
  maxRows: number;
16
16
  editable: boolean;
17
17
  showFilters: boolean;
18
+ /** Enable built-in context menu on cell right-click. */
19
+ showContextMenu: boolean;
18
20
  /** Enable row-level checkbox selection. */
19
21
  selectable: boolean;
20
22
  /** Row selection mode: 'single' or 'multi' (default: 'multi'). */
@@ -46,6 +48,7 @@ export declare class FlexTable extends LitElement {
46
48
  private _rowSelectionVersion;
47
49
  private _autocompleteState;
48
50
  private _headerMenu;
51
+ private _bodyContextMenu;
49
52
  private _viewDirty;
50
53
  private _isDragging;
51
54
  private _wasDrag;
@@ -152,7 +155,7 @@ export declare class FlexTable extends LitElement {
152
155
  */
153
156
  exportToString(format: ExportFormat, options?: {
154
157
  selectionOnly?: boolean;
155
- }): string;
158
+ }): string | Uint8Array;
156
159
  /**
157
160
  * Export table data and trigger file download.
158
161
  */
@@ -228,6 +231,8 @@ export declare class FlexTable extends LitElement {
228
231
  private _onHeaderContextMenu;
229
232
  private _showHiddenBefore;
230
233
  private _renderHeaderContextMenu;
234
+ private _renderBodyContextMenu;
235
+ private _applySortFromMenu;
231
236
  private _adjustFilterDropdown;
232
237
  private _onFilterBtnClick;
233
238
  private _renderFilterDropdown;
@@ -1,2 +1,2 @@
1
- import { a as e, i as t, n, r, t as i } from "./flex-table-zJST_6sy.js";
1
+ import { a as e, i as t, n, r, t as i } from "./flex-table-fp3j2egA.js";
2
2
  export { i as FlexTable, t as RowSelectionState, r as UndoStack, n as exportData, e as renderCell };
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as e } from "./flex-table-zJST_6sy.js";
1
+ import { t as e } from "./flex-table-fp3j2egA.js";
2
2
  import t from "react";
3
3
  import { createComponent as n } from "@lit/react";
4
4
  //#region src/react.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iyulab/flex-table",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "A minimalist, input-centric data grid web component",
5
5
  "type": "module",
6
6
  "main": "./dist/flex-table.js",