@iyulab/flex-table 0.12.0 → 0.13.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.
@@ -236,6 +236,36 @@ var s = t`
236
236
  appearance: auto;
237
237
  }
238
238
 
239
+ .ft-autocomplete-dropdown {
240
+ position: absolute;
241
+ top: 100%;
242
+ left: 0;
243
+ z-index: 100;
244
+ min-width: 100%;
245
+ max-height: 180px;
246
+ overflow-y: auto;
247
+ background: var(--ft-editor-bg, #fff);
248
+ border: 1px solid var(--ft-active-color);
249
+ border-top: none;
250
+ box-shadow: 0 4px 8px rgba(0,0,0,0.12);
251
+ }
252
+
253
+ .ft-autocomplete-item {
254
+ padding: 4px 8px;
255
+ cursor: pointer;
256
+ white-space: nowrap;
257
+ overflow: hidden;
258
+ text-overflow: ellipsis;
259
+ font-size: var(--ft-font-size, 14px);
260
+ color: var(--ft-text-color);
261
+ }
262
+
263
+ .ft-autocomplete-item:hover,
264
+ .ft-autocomplete-item.ft-autocomplete-active {
265
+ background: var(--ft-selection-bg);
266
+ color: var(--ft-active-color);
267
+ }
268
+
239
269
  .ft-cell.ft-type-number {
240
270
  text-align: right;
241
271
  font-variant-numeric: tabular-nums;
@@ -439,6 +469,56 @@ var s = t`
439
469
  box-sizing: border-box;
440
470
  }
441
471
 
472
+ /* Header context menu */
473
+ .ft-header-menu {
474
+ background: var(--ft-editor-bg, #fff);
475
+ border: 1px solid var(--ft-border-color);
476
+ box-shadow: 0 2px 8px rgba(0,0,0,0.16);
477
+ min-width: 160px;
478
+ border-radius: 4px;
479
+ overflow: hidden;
480
+ }
481
+
482
+ .ft-header-menu-item {
483
+ padding: 6px 12px;
484
+ cursor: pointer;
485
+ font-size: var(--ft-font-size, 14px);
486
+ color: var(--ft-text-color);
487
+ white-space: nowrap;
488
+ }
489
+
490
+ .ft-header-menu-item:hover {
491
+ background: var(--ft-selection-bg);
492
+ color: var(--ft-active-color);
493
+ }
494
+
495
+ /* Hidden column indicator in header */
496
+ .ft-hidden-col-indicator {
497
+ position: absolute;
498
+ left: 0;
499
+ top: 0;
500
+ height: 100%;
501
+ width: 16px;
502
+ background: var(--ft-selection-bg);
503
+ border-right: 2px solid var(--ft-active-color);
504
+ cursor: pointer;
505
+ display: flex;
506
+ align-items: center;
507
+ justify-content: center;
508
+ font-size: 10px;
509
+ color: var(--ft-active-color);
510
+ padding: 0;
511
+ border-top: none;
512
+ border-bottom: none;
513
+ border-left: none;
514
+ flex-shrink: 0;
515
+ }
516
+
517
+ .ft-hidden-col-indicator:hover {
518
+ background: var(--ft-active-color);
519
+ color: white;
520
+ }
521
+
442
522
  /* Find / Replace panel */
443
523
  .ft-find-panel {
444
524
  position: sticky;
@@ -524,30 +604,60 @@ var s = t`
524
604
  }
525
605
  `;
526
606
  //#endregion
607
+ //#region src/renderers/format.ts
608
+ function c(e, t) {
609
+ return e == null ? "" : typeof t == "function" ? t(e) : l(t) ? d(e, t) : u(e, t);
610
+ }
611
+ function l(e) {
612
+ return /[yMdHhms]/.test(e);
613
+ }
614
+ function u(e, t) {
615
+ let n = typeof e == "number" ? e : Number(e);
616
+ if (isNaN(n)) return String(e);
617
+ let r = t.endsWith("%"), i = t.match(/^([^#0.,%]+)/), a = i ? i[1] : "", o = t.replace(/^[^#0.,%]+/, "").replace(/%$/, ""), s = o.match(/\.([0#]+)$/), c = s ? s[1].length : 0, l = {
618
+ minimumFractionDigits: c,
619
+ maximumFractionDigits: c,
620
+ useGrouping: o.includes(",")
621
+ }, u;
622
+ return r ? u = new Intl.NumberFormat(void 0, {
623
+ ...l,
624
+ style: "percent"
625
+ }).format(n) : (u = new Intl.NumberFormat(void 0, l).format(n), a && (u = a + u)), u + "";
626
+ }
627
+ function d(e, t) {
628
+ let n;
629
+ if (e instanceof Date) n = e;
630
+ else if (typeof e == "string" || typeof e == "number") n = new Date(e);
631
+ else return String(e);
632
+ if (isNaN(n.getTime())) return String(e);
633
+ let r = String(n.getFullYear()), i = r.slice(-2), a = String(n.getMonth() + 1).padStart(2, "0"), o = String(n.getDate()).padStart(2, "0"), s = String(n.getHours()).padStart(2, "0"), c = String(n.getHours() % 12 || 12).padStart(2, "0"), l = String(n.getMinutes()).padStart(2, "0"), u = String(n.getSeconds()).padStart(2, "0");
634
+ return t.replace("yyyy", r).replace("yy", i).replace("MM", a).replace("dd", o).replace("HH", s).replace("hh", c).replace("mm", l).replace("ss", u);
635
+ }
636
+ //#endregion
527
637
  //#region src/renderers/cell-renderer.ts
528
- function c(e, t, n) {
529
- return n.renderer ? n.renderer(e, t, n) : l(e, n);
638
+ function f(e, t, n) {
639
+ return n.renderer ? n.renderer(e, t, n) : n.format ? typeof n.format == "function" ? n.format(e, t, n) : c(e, n.format) : p(e, n);
530
640
  }
531
- function l(e, t) {
641
+ function p(e, t) {
532
642
  if (e == null) return "";
533
643
  switch (t.type) {
534
- case "number": return d(e);
644
+ case "number": return h(e);
535
645
  case "boolean": return n`<span class="ft-bool">${e ? "✔" : ""}</span>`;
536
- case "date": return f(e);
537
- case "datetime": return p(e);
538
- case "select": return u(e, t);
646
+ case "date": return g(e);
647
+ case "datetime": return _(e);
648
+ case "select": return m(e, t);
539
649
  default: return String(e);
540
650
  }
541
651
  }
542
- function u(e, t) {
652
+ function m(e, t) {
543
653
  if (!t.options || t.options.length === 0 || typeof t.options[0] == "string") return e == null ? "" : String(e);
544
654
  let n = t.options.find((t) => t.value === e);
545
655
  return n ? n.label : e == null ? "" : String(e);
546
656
  }
547
- function d(e) {
657
+ function h(e) {
548
658
  return typeof e == "number" ? Number.isInteger(e) ? e.toLocaleString() : e.toLocaleString(void 0, { minimumFractionDigits: 1 }) : String(e);
549
659
  }
550
- function f(e) {
660
+ function g(e) {
551
661
  if (e instanceof Date) return e.toLocaleDateString();
552
662
  if (typeof e == "string") {
553
663
  let t = new Date(e);
@@ -555,7 +665,7 @@ function f(e) {
555
665
  }
556
666
  return String(e);
557
667
  }
558
- function p(e) {
668
+ function _(e) {
559
669
  if (e instanceof Date) return e.toLocaleString();
560
670
  if (typeof e == "string") {
561
671
  let t = new Date(e);
@@ -565,7 +675,7 @@ function p(e) {
565
675
  }
566
676
  //#endregion
567
677
  //#region src/core/selection.ts
568
- function m(e) {
678
+ function v(e) {
569
679
  return {
570
680
  startRow: Math.min(e.startRow, e.endRow),
571
681
  startCol: Math.min(e.startCol, e.endCol),
@@ -573,7 +683,7 @@ function m(e) {
573
683
  endCol: Math.max(e.startCol, e.endCol)
574
684
  };
575
685
  }
576
- var h = class {
686
+ var y = class {
577
687
  constructor() {
578
688
  this.activeCell = null, this.rangeAnchor = null, this.range = null, this.extraRanges = [], this._rowCount = 0, this._colCount = 0;
579
689
  }
@@ -600,7 +710,7 @@ var h = class {
600
710
  return e < 0 || e >= this._rowCount || t < 0 || t >= this._colCount ? this.activeCell : (!this.rangeAnchor && this.activeCell && (this.rangeAnchor = { ...this.activeCell }), this.activeCell = {
601
711
  row: e,
602
712
  col: t
603
- }, this.rangeAnchor && (this.range = m({
713
+ }, this.rangeAnchor && (this.range = v({
604
714
  startRow: this.rangeAnchor.row,
605
715
  startCol: this.rangeAnchor.col,
606
716
  endRow: e,
@@ -671,7 +781,7 @@ var h = class {
671
781
  shiftMoveRight() {
672
782
  return this.activeCell ? this.setActiveWithRange(this.activeCell.row, this.activeCell.col + 1) : null;
673
783
  }
674
- }, g = class {
784
+ }, b = class {
675
785
  constructor() {
676
786
  this.current = null;
677
787
  }
@@ -692,7 +802,7 @@ var h = class {
692
802
  let e = this.current;
693
803
  return this.current = null, e;
694
804
  }
695
- }, _ = class {
805
+ }, x = class {
696
806
  constructor() {
697
807
  this._selected = /* @__PURE__ */ new Set(), this._mode = "multi", this._rowCount = 0;
698
808
  }
@@ -745,7 +855,7 @@ var h = class {
745
855
  };
746
856
  //#endregion
747
857
  //#region src/core/sorting.ts
748
- function v(e, t, n) {
858
+ function S(e, t, n) {
749
859
  let r = Array.from({ length: e.length }, (e, t) => t);
750
860
  if (t.length === 0) return r;
751
861
  let i = /* @__PURE__ */ new Map();
@@ -756,13 +866,13 @@ function v(e, t, n) {
756
866
  if (c && l) continue;
757
867
  if (c) return 1;
758
868
  if (l) return -1;
759
- let u = y(t, s, i.get(a) ?? "text");
869
+ let u = C(t, s, i.get(a) ?? "text");
760
870
  if (u !== 0) return o === "asc" ? u : -u;
761
871
  }
762
872
  return n - r;
763
873
  }), r;
764
874
  }
765
- function y(e, t, n) {
875
+ function C(e, t, n) {
766
876
  switch (n) {
767
877
  case "number": {
768
878
  let n = Number(e), r = Number(t);
@@ -777,7 +887,7 @@ function y(e, t, n) {
777
887
  default: return String(e).localeCompare(String(t));
778
888
  }
779
889
  }
780
- function b(e, t, n) {
890
+ function w(e, t, n) {
781
891
  let r = e.find((e) => e.key === t);
782
892
  return n ? r ? r.direction === "asc" ? e.map((e) => e.key === t ? {
783
893
  ...e,
@@ -795,7 +905,7 @@ function b(e, t, n) {
795
905
  }
796
906
  //#endregion
797
907
  //#region src/core/filtering.ts
798
- function x(e, t, n) {
908
+ function T(e, t, n) {
799
909
  if (t.length === 0) return Array.from({ length: e.length }, (e, t) => t);
800
910
  let r = [];
801
911
  for (let i = 0; i < e.length; i++) {
@@ -814,9 +924,9 @@ function x(e, t, n) {
814
924
  }
815
925
  //#endregion
816
926
  //#region src/core/undo.ts
817
- var S = 100, C = class {
927
+ var E = 100, D = class {
818
928
  constructor() {
819
- this._undoStack = [], this._redoStack = [], this._maxSize = S;
929
+ this._undoStack = [], this._redoStack = [], this._maxSize = E;
820
930
  }
821
931
  get maxSize() {
822
932
  return this._maxSize;
@@ -853,7 +963,7 @@ var S = 100, C = class {
853
963
  };
854
964
  //#endregion
855
965
  //#region src/clipboard/clipboard.ts
856
- function w(e, t, n) {
966
+ function O(e, t, n) {
857
967
  let r = [];
858
968
  for (let i = n.startRow; i <= n.endRow; i++) {
859
969
  let a = e[i], o = [];
@@ -865,7 +975,7 @@ function w(e, t, n) {
865
975
  }
866
976
  return r.join("\n");
867
977
  }
868
- function T(e) {
978
+ function k(e) {
869
979
  let t = e.replace(/\r\n/g, "\n").replace(/\r/g, "\n"), n = [], r = [], i = 0, a = t.length;
870
980
  for (; i <= a;) {
871
981
  if (i === a) {
@@ -894,7 +1004,7 @@ function T(e) {
894
1004
  }
895
1005
  return n;
896
1006
  }
897
- function E(e, t) {
1007
+ function A(e, t) {
898
1008
  if (e === "") return null;
899
1009
  switch (t.type) {
900
1010
  case "number": {
@@ -907,23 +1017,23 @@ function E(e, t) {
907
1017
  }
908
1018
  //#endregion
909
1019
  //#region src/export/export.ts
910
- function D(e, t, n) {
1020
+ function j(e, t, n) {
911
1021
  switch (n) {
912
- case "csv": return k(e, t, ",");
913
- case "tsv": return k(e, t, " ");
914
- case "json": return j(e, t);
1022
+ case "csv": return N(e, t, ",");
1023
+ case "tsv": return N(e, t, " ");
1024
+ case "json": return F(e, t);
915
1025
  }
916
1026
  }
917
- function O(e, t) {
1027
+ function M(e, t) {
918
1028
  return e == null ? "" : (t.type === "date" || t.type === "datetime") && e instanceof Date ? e.toISOString() : String(e);
919
1029
  }
920
- function k(e, t, n) {
921
- return [t.map((e) => A(e.header, n)).join(n), ...e.map((e) => t.map((t) => A(O(e[t.key], t), n)).join(n))].join("\n");
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");
922
1032
  }
923
- function A(e, t) {
1033
+ function P(e, t) {
924
1034
  return e.includes(t) || e.includes("\"") || e.includes("\n") || e.includes("\r") ? "\"" + e.replace(/"/g, "\"\"") + "\"" : e;
925
1035
  }
926
- function j(e, t) {
1036
+ function F(e, t) {
927
1037
  let n = e.map((e) => {
928
1038
  let n = {};
929
1039
  for (let r of t) {
@@ -934,28 +1044,28 @@ function j(e, t) {
934
1044
  });
935
1045
  return JSON.stringify(n, null, 2);
936
1046
  }
937
- function M(e, t, n) {
1047
+ function I(e, t, n) {
938
1048
  let r = new Blob([e], { type: n }), i = URL.createObjectURL(r), a = document.createElement("a");
939
1049
  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);
940
1050
  }
941
- var N = {
1051
+ var L = {
942
1052
  csv: "text/csv;charset=utf-8",
943
1053
  tsv: "text/tab-separated-values;charset=utf-8",
944
1054
  json: "application/json;charset=utf-8"
945
- }, P = {
1055
+ }, R = {
946
1056
  csv: ".csv",
947
1057
  tsv: ".tsv",
948
1058
  json: ".json"
949
1059
  };
950
- function F(e) {
951
- return N[e];
1060
+ function z(e) {
1061
+ return L[e];
952
1062
  }
953
- function I(e) {
954
- return P[e];
1063
+ function B(e) {
1064
+ return R[e];
955
1065
  }
956
1066
  //#endregion
957
1067
  //#region \0@oxc-project+runtime@0.130.0/helpers/decorate.js
958
- function L(e, t, n, r) {
1068
+ function V(e, t, n, r) {
959
1069
  var i = arguments.length, a = i < 3 ? t : r === null ? r = Object.getOwnPropertyDescriptor(t, n) : r, o;
960
1070
  if (typeof Reflect == "object" && typeof Reflect.decorate == "function") a = Reflect.decorate(e, t, n, r);
961
1071
  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);
@@ -963,9 +1073,9 @@ function L(e, t, n, r) {
963
1073
  }
964
1074
  //#endregion
965
1075
  //#region src/flex-table.ts
966
- var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1076
+ var H = 120, U = 40, W = 32, G = 5, K = class extends e {
967
1077
  constructor(...e) {
968
- super(...e), this.columns = [], this.data = [], this.rowHeight = B, 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 h(), this._editing = new g(), this._rowSelection = new _(), this._undo = new C(), this._filters = [], this._filteredIndices = [], this._sortedIndices = [], this._openFilterKey = null, this._rowSelectionVersion = 0, 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();
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();
969
1079
  }
970
1080
  static {
971
1081
  this.styles = s;
@@ -1149,6 +1259,29 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1149
1259
  composed: !0
1150
1260
  })), this._dispatchUndoStateEvent();
1151
1261
  }
1262
+ hideColumn(e) {
1263
+ this._setColumnHidden(e, !0);
1264
+ }
1265
+ showColumn(e) {
1266
+ this._setColumnHidden(e, !1);
1267
+ }
1268
+ _setColumnHidden(e, t) {
1269
+ let n = this.columns.findIndex((t) => t.key === e);
1270
+ n !== -1 && (this.columns = this.columns.map((e, r) => r === n ? {
1271
+ ...e,
1272
+ hidden: t
1273
+ } : e), this.dispatchEvent(new CustomEvent("column-visibility-change", {
1274
+ detail: {
1275
+ key: e,
1276
+ hidden: t
1277
+ },
1278
+ bubbles: !0,
1279
+ composed: !0
1280
+ })));
1281
+ }
1282
+ getHiddenColumns() {
1283
+ return this.columns.filter((e) => e.hidden);
1284
+ }
1152
1285
  addRow(e, t) {
1153
1286
  if (this.maxRows > 0 && this.data.length >= this.maxRows) return null;
1154
1287
  let n = e ?? this._createEmptyRow(), r = t ?? this.data.length;
@@ -1251,12 +1384,12 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1251
1384
  if (!t) return "";
1252
1385
  let n = this.visibleColumns.slice(t.startCol, t.endCol + 1), r = [];
1253
1386
  for (let e = t.startRow; e <= t.endRow; e++) r.push(this.data[this._toDataIndex(e)]);
1254
- return D(r, n, e);
1387
+ return j(r, n, e);
1255
1388
  }
1256
- return D(this._sortedIndices.map((e) => this.data[e]), this.visibleColumns, e);
1389
+ return j(this._sortedIndices.map((e) => this.data[e]), this.visibleColumns, e);
1257
1390
  }
1258
1391
  exportToFile(e, t) {
1259
- M(this.exportToString(e), t ?? `export${I(e)}`, F(e));
1392
+ I(this.exportToString(e), t ?? `export${B(e)}`, z(e));
1260
1393
  }
1261
1394
  _getSelectedDataRows() {
1262
1395
  let e = this._selection.getEffectiveRange();
@@ -1269,8 +1402,8 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1269
1402
  return this._columnWidths.get(e);
1270
1403
  }
1271
1404
  _getColWidth(e) {
1272
- let t = this._columnWidths.get(e.key) ?? e.width ?? R;
1273
- return Math.max(t, e.minWidth ?? z);
1405
+ let t = this._columnWidths.get(e.key) ?? e.width ?? H;
1406
+ return Math.max(t, e.minWidth ?? U);
1274
1407
  }
1275
1408
  get headerHeight() {
1276
1409
  return this.rowHeight + 8;
@@ -1282,10 +1415,10 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1282
1415
  return this._visibleRowCount * this.rowHeight;
1283
1416
  }
1284
1417
  get visibleRange() {
1285
- let e = Math.max(0, this._scrollTop - this.headerHeight), t = Math.max(0, Math.floor(e / this.rowHeight) - V), n = Math.ceil(this._viewportHeight / this.rowHeight);
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);
1286
1419
  return {
1287
1420
  start: t,
1288
- end: Math.min(this._visibleRowCount, Math.floor(e / this.rowHeight) + n + V)
1421
+ end: Math.min(this._visibleRowCount, Math.floor(e / this.rowHeight) + n + G)
1289
1422
  };
1290
1423
  }
1291
1424
  connectedCallback() {
@@ -1318,13 +1451,13 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1318
1451
  i = r;
1319
1452
  break;
1320
1453
  }
1321
- i = Math.max(0, i - V);
1454
+ i = Math.max(0, i - G);
1322
1455
  let a = e.length;
1323
1456
  for (let e = i; e < t.length; e++) if (t[e] > r) {
1324
1457
  a = e;
1325
1458
  break;
1326
1459
  }
1327
- return a = Math.min(e.length, a + V), {
1460
+ return a = Math.min(e.length, a + G), {
1328
1461
  start: i,
1329
1462
  end: a
1330
1463
  };
@@ -1340,7 +1473,7 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1340
1473
  this._filteredIndices = Array.from({ length: this.data.length }, (e, t) => t), this._sortedIndices = this._filteredIndices;
1341
1474
  return;
1342
1475
  }
1343
- if (this._filteredIndices = x(this.data, this._filters, (e, t, n) => {
1476
+ if (this._filteredIndices = T(this.data, this._filters, (e, t, n) => {
1344
1477
  this.dispatchEvent(new CustomEvent("filter-error", {
1345
1478
  detail: {
1346
1479
  error: e,
@@ -1353,7 +1486,7 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1353
1486
  }), this._filters.length === 0 && this._sortCriteria.length === 0) this._sortedIndices = Array.from({ length: this.data.length }, (e, t) => t);
1354
1487
  else if (this._sortCriteria.length === 0) this._sortedIndices = [...this._filteredIndices];
1355
1488
  else {
1356
- let e = v(this._filteredIndices.map((e) => this.data[e]), this._sortCriteria, this.visibleColumns);
1489
+ let e = S(this._filteredIndices.map((e) => this.data[e]), this._sortCriteria, this.visibleColumns);
1357
1490
  this._sortedIndices = e.map((e) => this._filteredIndices[e]);
1358
1491
  }
1359
1492
  }
@@ -1373,7 +1506,7 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1373
1506
  this._scrollTop = this.scrollTop, this._scrollLeft = this.scrollLeft;
1374
1507
  }
1375
1508
  _onDocumentClick() {
1376
- this._openFilterKey &&= null;
1509
+ this._openFilterKey &&= null, this._headerMenu &&= null;
1377
1510
  }
1378
1511
  _onContextMenu(e) {
1379
1512
  let t = e.composedPath().find((e) => e instanceof HTMLElement && e.classList.contains("ft-cell"));
@@ -1467,14 +1600,30 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1467
1600
  }
1468
1601
  let t = this.shadowRoot?.querySelector(".ft-editor");
1469
1602
  if (t) {
1470
- let n = E(t.value, e);
1603
+ let n = A(t.value, e);
1471
1604
  this._applyEdit(n);
1472
1605
  } else this._cancelEdit();
1473
1606
  }
1474
1607
  _applyEdit(e) {
1608
+ this._autocompleteState = null;
1475
1609
  let t = this._editing.commit();
1476
1610
  if (this._editingCell = null, !t) return;
1477
1611
  let { row: n, col: r } = t.position, i = this._toDataIndex(n), a = this.visibleColumns[r], o = t.originalValue;
1612
+ if (a.autocomplete === "strict" && e != null && e !== "" && !this._getAutocompleteCandidates(a, "").includes(String(e))) {
1613
+ let t = "Value must be from the existing list";
1614
+ this._markCellInvalid(n, r, t), this.dispatchEvent(new CustomEvent("validation-error", {
1615
+ detail: {
1616
+ row: i,
1617
+ col: r,
1618
+ key: a.key,
1619
+ value: e,
1620
+ error: t
1621
+ },
1622
+ bubbles: !0,
1623
+ composed: !0
1624
+ }));
1625
+ return;
1626
+ }
1478
1627
  if (a.validator) {
1479
1628
  let t = a.validator(e, this.data[i], a);
1480
1629
  if (t) {
@@ -1513,6 +1662,7 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1513
1662
  })), this._dispatchUndoStateEvent();
1514
1663
  }
1515
1664
  _cancelEdit() {
1665
+ this._autocompleteState = null;
1516
1666
  let e = this._editing.cancel();
1517
1667
  this._editingCell = null, e && this.dispatchEvent(new CustomEvent("cell-edit-cancel", {
1518
1668
  detail: {
@@ -1524,8 +1674,56 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1524
1674
  }));
1525
1675
  }
1526
1676
  _onEditorKeyDown(e) {
1677
+ if (this._autocompleteState && this._autocompleteState.candidates.length > 0) {
1678
+ if (e.key === "ArrowDown") {
1679
+ e.preventDefault(), e.stopPropagation();
1680
+ let t = this._autocompleteState.candidates.length;
1681
+ this._autocompleteState = {
1682
+ ...this._autocompleteState,
1683
+ activeIndex: Math.min(this._autocompleteState.activeIndex + 1, t - 1)
1684
+ };
1685
+ return;
1686
+ }
1687
+ if (e.key === "ArrowUp") {
1688
+ e.preventDefault(), e.stopPropagation(), this._autocompleteState = {
1689
+ ...this._autocompleteState,
1690
+ activeIndex: Math.max(this._autocompleteState.activeIndex - 1, -1)
1691
+ };
1692
+ return;
1693
+ }
1694
+ if (e.key === "Enter" && this._autocompleteState.activeIndex >= 0) {
1695
+ e.preventDefault(), e.stopPropagation();
1696
+ let t = this._autocompleteState.candidates[this._autocompleteState.activeIndex];
1697
+ this._selectAutocompleteCandidate(t);
1698
+ return;
1699
+ }
1700
+ if (e.key === "Escape") {
1701
+ e.preventDefault(), e.stopPropagation(), this._autocompleteState = null;
1702
+ return;
1703
+ }
1704
+ }
1527
1705
  e.key === "Enter" ? (e.preventDefault(), e.stopPropagation(), this._commitEdit(), this._selection.moveDown(), this._syncActiveCell()) : e.key === "Tab" ? (e.preventDefault(), e.stopPropagation(), this._commitEdit(), e.shiftKey ? this._selection.movePrev() : this._selection.moveNext(), this._syncActiveCell()) : e.key === "Escape" && (e.preventDefault(), e.stopPropagation(), this._cancelEdit());
1528
1706
  }
1707
+ _getAutocompleteCandidates(e, t) {
1708
+ let n = t.toLowerCase(), r = /* @__PURE__ */ new Set(), i = [];
1709
+ for (let a of this.data) {
1710
+ let o = a[e.key];
1711
+ if (o == null) continue;
1712
+ let s = String(o);
1713
+ if (!r.has(s) && (t === "" || s.toLowerCase().includes(n)) && (r.add(s), i.push(s), i.length >= 20)) break;
1714
+ }
1715
+ return i;
1716
+ }
1717
+ _onAutocompleteInput(e, t) {
1718
+ let n = e.target.value, r = this._getAutocompleteCandidates(t, n).filter((e) => e !== n);
1719
+ this._autocompleteState = r.length > 0 ? {
1720
+ candidates: r,
1721
+ activeIndex: -1
1722
+ } : null;
1723
+ }
1724
+ _selectAutocompleteCandidate(e) {
1725
+ this._autocompleteState = null, this._applyEdit(e), this._selection.moveDown(), this._syncActiveCell();
1726
+ }
1529
1727
  _syncActiveCell() {
1530
1728
  this._activeCell = this._selection.activeCell ? { ...this._selection.activeCell } : null, this._scrollToActiveCell(), this._dispatchSelectionEvent();
1531
1729
  }
@@ -1595,7 +1793,7 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1595
1793
  e.preventDefault();
1596
1794
  let n = t[this._activeCell.col];
1597
1795
  if (!n) return !0;
1598
- let r = this._columnWidths.get(n.key) ?? n.width ?? R, i = e.key === "ArrowRight" ? 20 : -20, a = n.minWidth ?? z, o = Math.max(a, r + i);
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);
1599
1797
  return this._columnWidths.set(n.key, o), this.requestUpdate(), this.dispatchEvent(new CustomEvent("column-resize", {
1600
1798
  detail: {
1601
1799
  key: n.key,
@@ -1624,7 +1822,7 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1624
1822
  async _handleCopy(e) {
1625
1823
  let t = this._selection.getEffectiveRange();
1626
1824
  if (!t) return;
1627
- let n = this.visibleColumns, r = w(this._sortedIndices.map((e) => this.data[e]), n, t);
1825
+ let n = this.visibleColumns, r = O(this._sortedIndices.map((e) => this.data[e]), n, t);
1628
1826
  try {
1629
1827
  await navigator.clipboard.writeText(r);
1630
1828
  } catch (e) {
@@ -1650,7 +1848,7 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1650
1848
  if (!this._activeCell) return;
1651
1849
  let e = await this._readClipboardText();
1652
1850
  if (e == null) return;
1653
- let t = T(e);
1851
+ let t = k(e);
1654
1852
  if (t.length === 0) return;
1655
1853
  let n = this._expandRowsForPaste(this._activeCell.row, t.length), r = this._applyPasteData(this._activeCell, t);
1656
1854
  if (r.length > 0 || n.length > 0) {
@@ -1708,7 +1906,7 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1708
1906
  for (let a = 0; a < t[i].length; a++) {
1709
1907
  let s = e.col + a;
1710
1908
  if (s >= n.length) break;
1711
- let c = n[s], l = this.data[o][c.key], u = E(t[i][a], c);
1909
+ let c = n[s], l = this.data[o][c.key], u = A(t[i][a], c);
1712
1910
  this.data[o][c.key] = u, r.push({
1713
1911
  row: o,
1714
1912
  col: s,
@@ -1773,7 +1971,7 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1773
1971
  for (let r = e.startCol + 1; r <= e.endCol; r++) {
1774
1972
  let e = t[r];
1775
1973
  if (!e || !this._isCellEditable(e)) continue;
1776
- let a = E(o == null ? "" : String(o), e);
1974
+ let a = A(o == null ? "" : String(o), e);
1777
1975
  n.push({
1778
1976
  row: i,
1779
1977
  key: e.key,
@@ -1864,7 +2062,7 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1864
2062
  this._selectColumn(n);
1865
2063
  return;
1866
2064
  }
1867
- t.sortable !== !1 && (this._sortCriteria = b(this._sortCriteria, t.key, e.shiftKey), this.dataMode !== "server" && this._recomputeView(), this.requestUpdate(), this.dispatchEvent(new CustomEvent("sort-change", {
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", {
1868
2066
  detail: { criteria: [...this._sortCriteria] },
1869
2067
  bubbles: !0,
1870
2068
  composed: !0
@@ -1906,15 +2104,25 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1906
2104
  c || l ? "ft-pinned" : ""
1907
2105
  ].filter(Boolean).join(" "), d = i ? a ? a.direction === "asc" ? "ascending" : "descending" : "none" : void 0, f = this._getColWidth(e), p = this.headerHeight, m = this._colLeftOffsets[t] ?? 0, h;
1908
2106
  h = c ? `position: absolute; top: 0; left: ${this._scrollLeft + this._getPinnedLeft(t)}px; width: ${f}px; height: ${p}px; z-index: 4;` : l ? `position: absolute; top: 0; right: ${-this._scrollLeft + this._getPinnedRight(t)}px; width: ${f}px; height: ${p}px; z-index: 4;` : `left: ${m}px; width: ${f}px; height: ${p}px;`;
1909
- let g = this._colDrag?.active && this._colDrag.colIndex === t;
2107
+ let g = this._colDrag?.active && this._colDrag.colIndex === t, _ = [...u.split(" "), g ? "ft-col-dragging" : ""].filter(Boolean).join(" "), v = this.columns, y = v.findIndex((t) => t.key === e.key), b = [];
2108
+ if (y > 0) for (let e = y - 1; e >= 0 && v[e].hidden; e--) b.push(v[e]);
2109
+ let x = b.length > 0;
1910
2110
  return n`
1911
- <div class=${[...u.split(" "), g ? "ft-col-dragging" : ""].filter(Boolean).join(" ")}
2111
+ <div class=${_}
1912
2112
  role="columnheader"
1913
2113
  data-col-index=${t}
1914
2114
  style=${h}
1915
2115
  aria-sort=${d ?? r}
2116
+ @contextmenu=${(t) => this._onHeaderContextMenu(t, e)}
1916
2117
  @mousedown=${(n) => this._onHeaderMouseDown(n, e, t)}
1917
2118
  @click=${i ? (t) => this._onHeaderClick(t, e) : void 0}>
2119
+ ${x ? n`
2120
+ <button class="ft-hidden-col-indicator"
2121
+ title="Show hidden column(s)"
2122
+ @click=${(t) => {
2123
+ t.stopPropagation(), this._showHiddenBefore(e);
2124
+ }}>&#x276F;</button>
2125
+ ` : ""}
1918
2126
  <span>${e.header}</span>
1919
2127
  ${a ? n`<span class="ft-sort-indicator">${a.direction === "asc" ? "▲" : "▼"}</span>` : ""}
1920
2128
  ${o >= 0 ? n`<span class="ft-sort-order">${o + 1}</span>` : ""}
@@ -1935,6 +2143,56 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
1935
2143
  </div>
1936
2144
  ${this._openFilterKey === e.key ? this._renderFilterDropdown(e) : ""}
1937
2145
  `;
2146
+ }
2147
+ _onHeaderContextMenu(e, t) {
2148
+ e.preventDefault();
2149
+ let n = this.columns, r = n.findIndex((e) => e.key === t.key), i = [];
2150
+ for (let e = r - 1; e >= 0 && n[e].hidden; e--) i.push(n[e]);
2151
+ for (let e = r + 1; e < n.length && n[e].hidden; e++) i.push(n[e]);
2152
+ this._headerMenu = {
2153
+ key: t.key,
2154
+ x: e.clientX,
2155
+ y: e.clientY,
2156
+ hiddenNeighbors: i
2157
+ }, this.dispatchEvent(new CustomEvent("header-context-menu", {
2158
+ detail: {
2159
+ key: t.key,
2160
+ header: t.header,
2161
+ x: e.clientX,
2162
+ y: e.clientY
2163
+ },
2164
+ bubbles: !0,
2165
+ composed: !0
2166
+ })), requestAnimationFrame(() => {
2167
+ document.addEventListener("click", this._onDocumentClick, { once: !0 });
2168
+ });
2169
+ }
2170
+ _showHiddenBefore(e) {
2171
+ let t = this.columns, n = t.findIndex((t) => t.key === e.key);
2172
+ for (let e = n - 1; e >= 0 && t[e].hidden; e--) this._setColumnHidden(t[e].key, !1);
2173
+ }
2174
+ _renderHeaderContextMenu() {
2175
+ if (!this._headerMenu) return r;
2176
+ let { key: e, x: t, y: i, hiddenNeighbors: a } = this._headerMenu;
2177
+ return this.columns.find((t) => t.key === e) ? n`
2178
+ <div class="ft-header-menu" style="position: fixed; left: ${t}px; top: ${i}px; z-index: 200;"
2179
+ @mousedown=${(e) => e.stopPropagation()}>
2180
+ <div class="ft-header-menu-item"
2181
+ @click=${() => {
2182
+ this._setColumnHidden(e, !0), this._headerMenu = null;
2183
+ }}>
2184
+ Hide column
2185
+ </div>
2186
+ ${a.map((e) => n`
2187
+ <div class="ft-header-menu-item"
2188
+ @click=${() => {
2189
+ this._setColumnHidden(e.key, !1), this._headerMenu = null;
2190
+ }}>
2191
+ Show: ${e.header}
2192
+ </div>
2193
+ `)}
2194
+ </div>
2195
+ ` : r;
1938
2196
  }
1939
2197
  _adjustFilterDropdown() {
1940
2198
  if (!this._openFilterKey) return;
@@ -2076,7 +2334,7 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
2076
2334
  if (!n) return;
2077
2335
  let r = this.shadowRoot?.querySelectorAll(`.ft-cell[data-col-index="${t}"]`), i = 0, a = this.shadowRoot?.querySelector(`.ft-header-cell[data-col-index="${t}"]`);
2078
2336
  if (a && (i = Math.max(i, a.scrollWidth)), r) for (let e of r) i = Math.max(i, e.scrollWidth);
2079
- let o = n.minWidth ?? z, s = Math.max(o, i + 8);
2337
+ let o = n.minWidth ?? U, s = Math.max(o, i + 8);
2080
2338
  this._columnWidths.set(n.key, s), this.requestUpdate(), this.dispatchEvent(new CustomEvent("column-resize", {
2081
2339
  detail: {
2082
2340
  key: n.key,
@@ -2089,7 +2347,7 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
2089
2347
  }
2090
2348
  _onResizeStart(e, t) {
2091
2349
  e.preventDefault(), e.stopPropagation();
2092
- let n = this.visibleColumns[t], r = this._columnWidths.get(n.key) ?? n.width ?? R;
2350
+ let n = this.visibleColumns[t], r = this._columnWidths.get(n.key) ?? n.width ?? H;
2093
2351
  this._resizing = {
2094
2352
  colIndex: t,
2095
2353
  startX: e.clientX,
@@ -2097,13 +2355,13 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
2097
2355
  };
2098
2356
  let i = (e) => {
2099
2357
  if (!this._resizing) return;
2100
- let t = e.clientX - this._resizing.startX, r = n.minWidth ?? z, i = Math.max(r, this._resizing.startWidth + t);
2358
+ let t = e.clientX - this._resizing.startX, r = n.minWidth ?? U, i = Math.max(r, this._resizing.startWidth + t);
2101
2359
  this._columnWidths.set(n.key, i), this.requestUpdate();
2102
2360
  }, a = () => {
2103
2361
  document.removeEventListener("mousemove", i), document.removeEventListener("mouseup", o), this._resizeCleanup = null;
2104
2362
  }, o = () => {
2105
2363
  if (a(), this._resizing) {
2106
- let e = this._columnWidths.get(n.key) ?? R;
2364
+ let e = this._columnWidths.get(n.key) ?? H;
2107
2365
  this.dispatchEvent(new CustomEvent("column-resize", {
2108
2366
  detail: {
2109
2367
  key: n.key,
@@ -2553,6 +2811,7 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
2553
2811
  ${this.footerData ? this._renderFooter(i, a, o) : ""}
2554
2812
  ${_}
2555
2813
  ${g}
2814
+ ${this._renderHeaderContextMenu()}
2556
2815
  `;
2557
2816
  }
2558
2817
  _onSelectAllChange(e) {
@@ -2616,11 +2875,17 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
2616
2875
  `;
2617
2876
  }
2618
2877
  _renderCell(e, t, i, a) {
2619
- let o = this._activeCell?.row === i && this._activeCell?.col === a, s = this._editingCell?.row === i && this._editingCell?.col === a, l = this._selection.isInRange(i, a), u = t.pinned === "left", d = t.pinned === "right", f = u || d, p = this._getColWidth(t), m = this.rowHeight, h = this._colLeftOffsets[a] ?? 0, g;
2620
- g = u ? `position: absolute; top: 0; left: ${this._scrollLeft + this._getPinnedLeft(a)}px; width: ${p}px; height: ${m}px; z-index: 2;` : d ? `position: absolute; top: 0; right: ${-this._scrollLeft + this._getPinnedRight(a)}px; width: ${p}px; height: ${m}px; z-index: 2;` : `left: ${h}px; width: ${p}px; height: ${m}px;`;
2878
+ let o = this._activeCell?.row === i && this._activeCell?.col === a, s = this._editingCell?.row === i && this._editingCell?.col === a, c = this._selection.isInRange(i, a), l = t.pinned === "left", u = t.pinned === "right", d = l || u, p = this._getColWidth(t), m = this.rowHeight, h = this._colLeftOffsets[a] ?? 0, g;
2879
+ g = l ? `position: absolute; top: 0; left: ${this._scrollLeft + this._getPinnedLeft(a)}px; width: ${p}px; height: ${m}px; z-index: 2;` : u ? `position: absolute; top: 0; right: ${-this._scrollLeft + this._getPinnedRight(a)}px; width: ${p}px; height: ${m}px; z-index: 2;` : `left: ${h}px; width: ${p}px; height: ${m}px;`;
2621
2880
  let _ = !this._isCellEditable(t);
2881
+ if (t.conditionalRules && t.conditionalRules.length > 0) {
2882
+ let n = e[t.key], r = {};
2883
+ for (let i of t.conditionalRules) i.when(n, e, t) && (i.style.background && (r.background = i.style.background), i.style.color && (r.color = i.style.color), i.style.fontWeight && (r["font-weight"] = i.style.fontWeight), i.style.fontStyle && (r["font-style"] = i.style.fontStyle));
2884
+ let i = Object.entries(r).map(([e, t]) => `${e}: ${t}`).join("; ");
2885
+ i && (g += " " + i + ";");
2886
+ }
2622
2887
  if (s) return n`
2623
- <div class="ft-cell ft-editing ft-active ${f ? "ft-pinned" : ""}" role="gridcell"
2888
+ <div class="ft-cell ft-editing ft-active ${d ? "ft-pinned" : ""}" role="gridcell"
2624
2889
  data-col-index=${a}
2625
2890
  aria-selected="true"
2626
2891
  aria-readonly=${_ ? "true" : r}
@@ -2628,14 +2893,14 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
2628
2893
  ${this._renderEditor(e, t)}
2629
2894
  </div>
2630
2895
  `;
2631
- let v = o || l, y = this._isCellInvalid(i, a), b = (this._findState?.results ?? []).findIndex((e) => e.row === i && e.col === a), x = b >= 0, S = x && b === this._findState.currentIndex;
2896
+ let v = o || c, y = this._isCellInvalid(i, a), b = (this._findState?.results ?? []).findIndex((e) => e.row === i && e.col === a), x = b >= 0, S = x && b === this._findState.currentIndex;
2632
2897
  return n`
2633
2898
  <div class=${[
2634
2899
  "ft-cell",
2635
2900
  `ft-type-${t.type ?? "text"}`,
2636
2901
  o ? "ft-active" : "",
2637
- l ? "ft-selected" : "",
2638
- f ? "ft-pinned" : "",
2902
+ c ? "ft-selected" : "",
2903
+ d ? "ft-pinned" : "",
2639
2904
  y ? "ft-invalid" : "",
2640
2905
  S ? "ft-find-current" : x ? "ft-find-match" : ""
2641
2906
  ].filter(Boolean).join(" ")}
@@ -2650,45 +2915,67 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
2650
2915
  @mouseenter=${() => this._onCellMouseEnter(i, a)}
2651
2916
  @click=${(e) => this._onCellClickEvent(e, i, a)}
2652
2917
  @dblclick=${() => this._onCellDblClick(i, a)}>
2653
- ${c(e[t.key], e, t)}
2918
+ ${f(e[t.key], e, t)}
2654
2919
  </div>
2655
2920
  `;
2656
2921
  }
2657
2922
  _renderEditor(e, t) {
2658
2923
  if (t.editor) return t.editor(e[t.key], e, t);
2659
- let r = e[t.key], i = r == null ? "" : String(r);
2924
+ let i = e[t.key], a = i == null ? "" : String(i);
2660
2925
  if (t.type === "number") return n`
2661
2926
  <input class="ft-editor ft-editor-number" type="number"
2662
- .value=${i}
2927
+ .value=${a}
2663
2928
  @keydown=${this._onEditorKeyDown}
2664
2929
  @blur=${() => this._commitEdit()}>
2665
2930
  `;
2666
2931
  if (t.type === "date") return n`
2667
2932
  <input class="ft-editor" type="date"
2668
- .value=${this._toDateInputValue(r)}
2933
+ .value=${this._toDateInputValue(i)}
2669
2934
  @keydown=${this._onEditorKeyDown}
2670
2935
  @blur=${() => this._commitEdit()}>
2671
2936
  `;
2672
2937
  if (t.type === "datetime") return n`
2673
2938
  <input class="ft-editor" type="datetime-local"
2674
- .value=${this._toDateTimeInputValue(r)}
2939
+ .value=${this._toDateTimeInputValue(i)}
2675
2940
  @keydown=${this._onEditorKeyDown}
2676
2941
  @blur=${() => this._commitEdit()}>
2677
2942
  `;
2678
2943
  if (t.type === "select" && t.options && t.options.length > 0) {
2679
- let e = t.options, i = typeof e[0] == "string";
2944
+ let e = t.options, r = typeof e[0] == "string";
2680
2945
  return n`
2681
2946
  <select class="ft-editor"
2682
2947
  @keydown=${this._onEditorKeyDown}
2683
2948
  @blur=${() => this._commitEdit()}
2684
2949
  @change=${() => this._commitEdit()}>
2685
- ${i ? e.map((e) => n`<option value=${e} ?selected=${e === r}>${e}</option>`) : e.map((e) => n`<option value=${String(e.value)} ?selected=${e.value === r}>${e.label}</option>`)}
2950
+ ${r ? e.map((e) => n`<option value=${e} ?selected=${e === i}>${e}</option>`) : e.map((e) => n`<option value=${String(e.value)} ?selected=${e.value === i}>${e.label}</option>`)}
2686
2951
  </select>
2952
+ `;
2953
+ }
2954
+ if (t.autocomplete) {
2955
+ let e = this._autocompleteState?.candidates ?? [], i = this._autocompleteState?.activeIndex ?? -1;
2956
+ return n`
2957
+ <input class="ft-editor" type="text"
2958
+ .value=${a}
2959
+ @input=${(e) => this._onAutocompleteInput(e, t)}
2960
+ @keydown=${this._onEditorKeyDown}
2961
+ @blur=${() => this._commitEdit()}>
2962
+ ${e.length > 0 ? n`
2963
+ <div class="ft-autocomplete-dropdown">
2964
+ ${e.map((e, t) => n`
2965
+ <div class="ft-autocomplete-item ${t === i ? "ft-autocomplete-active" : ""}"
2966
+ @mousedown=${(t) => {
2967
+ t.preventDefault(), this._selectAutocompleteCandidate(e);
2968
+ }}>
2969
+ ${e}
2970
+ </div>
2971
+ `)}
2972
+ </div>
2973
+ ` : r}
2687
2974
  `;
2688
2975
  }
2689
2976
  return n`
2690
2977
  <input class="ft-editor" type="text"
2691
- .value=${i}
2978
+ .value=${a}
2692
2979
  @keydown=${this._onEditorKeyDown}
2693
2980
  @blur=${() => this._commitEdit()}>
2694
2981
  `;
@@ -2704,36 +2991,36 @@ var R = 120, z = 40, B = 32, V = 5, H = class extends e {
2704
2991
  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")}`;
2705
2992
  }
2706
2993
  };
2707
- L([a({ type: Array })], H.prototype, "columns", void 0), L([a({
2994
+ V([a({ type: Array })], K.prototype, "columns", void 0), V([a({
2708
2995
  type: Array,
2709
2996
  hasChanged: () => !0
2710
- })], H.prototype, "data", void 0), L([a({
2997
+ })], K.prototype, "data", void 0), V([a({
2711
2998
  type: Number,
2712
2999
  attribute: "row-height"
2713
- })], H.prototype, "rowHeight", void 0), L([a({
3000
+ })], K.prototype, "rowHeight", void 0), V([a({
2714
3001
  type: Boolean,
2715
3002
  attribute: "show-row-numbers"
2716
- })], H.prototype, "showRowNumbers", void 0), L([a({
3003
+ })], K.prototype, "showRowNumbers", void 0), V([a({
2717
3004
  type: String,
2718
3005
  reflect: !0
2719
- })], H.prototype, "theme", void 0), L([a({
3006
+ })], K.prototype, "theme", void 0), V([a({
2720
3007
  type: Number,
2721
3008
  attribute: "max-rows"
2722
- })], H.prototype, "maxRows", void 0), L([a({ type: Boolean })], H.prototype, "editable", void 0), L([a({
3009
+ })], K.prototype, "maxRows", void 0), V([a({ type: Boolean })], K.prototype, "editable", void 0), V([a({
2723
3010
  type: Boolean,
2724
3011
  attribute: "show-filters"
2725
- })], H.prototype, "showFilters", void 0), L([a({ type: Boolean })], H.prototype, "selectable", void 0), L([a({
3012
+ })], K.prototype, "showFilters", void 0), V([a({ type: Boolean })], K.prototype, "selectable", void 0), V([a({
2726
3013
  type: String,
2727
3014
  attribute: "selection-mode"
2728
- })], H.prototype, "selectionMode", null), L([a({
3015
+ })], K.prototype, "selectionMode", null), V([a({
2729
3016
  type: String,
2730
3017
  attribute: "data-mode"
2731
- })], H.prototype, "dataMode", void 0), L([a({
3018
+ })], K.prototype, "dataMode", void 0), V([a({
2732
3019
  type: Object,
2733
3020
  attribute: "footer-data"
2734
- })], H.prototype, "footerData", void 0), L([a({
3021
+ })], K.prototype, "footerData", void 0), V([a({
2735
3022
  type: Number,
2736
3023
  attribute: "max-undo-size"
2737
- })], H.prototype, "maxUndoSize", null), L([o()], H.prototype, "_scrollTop", void 0), L([o()], H.prototype, "_scrollLeft", void 0), L([o()], H.prototype, "_viewportHeight", void 0), L([o()], H.prototype, "_viewportWidth", void 0), L([o()], H.prototype, "_activeCell", void 0), L([o()], H.prototype, "_editingCell", void 0), L([o()], H.prototype, "_sortCriteria", void 0), L([o()], H.prototype, "_openFilterKey", void 0), L([o()], H.prototype, "_rowSelectionVersion", void 0), H = L([i("flex-table")], H);
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);
2738
3025
  //#endregion
2739
- export { c as a, _ as i, D as n, C as r, H as t };
3026
+ export { f as a, x as i, j as n, D as r, K as t };
@@ -44,6 +44,8 @@ export declare class FlexTable extends LitElement {
44
44
  private _sortedIndices;
45
45
  private _openFilterKey;
46
46
  private _rowSelectionVersion;
47
+ private _autocompleteState;
48
+ private _headerMenu;
47
49
  private _viewDirty;
48
50
  private _isDragging;
49
51
  private _wasDrag;
@@ -117,6 +119,13 @@ export declare class FlexTable extends LitElement {
117
119
  * @param newIndex Target index in the columns array.
118
120
  */
119
121
  moveColumn(key: string, newIndex: number): void;
122
+ /** Hide a column by its key. Fires `column-visibility-change` event. */
123
+ hideColumn(key: string): void;
124
+ /** Show a previously hidden column by its key. Fires `column-visibility-change` event. */
125
+ showColumn(key: string): void;
126
+ private _setColumnHidden;
127
+ /** Returns all columns marked as hidden. */
128
+ getHiddenColumns(): ColumnDefinition[];
120
129
  /**
121
130
  * Add a row at the specified index (default: end).
122
131
  * Returns the new row.
@@ -187,6 +196,9 @@ export declare class FlexTable extends LitElement {
187
196
  private _applyEdit;
188
197
  private _cancelEdit;
189
198
  private _onEditorKeyDown;
199
+ private _getAutocompleteCandidates;
200
+ private _onAutocompleteInput;
201
+ private _selectAutocompleteCandidate;
190
202
  private _syncActiveCell;
191
203
  private _onKeyDown;
192
204
  private _handleCtrlKey;
@@ -213,6 +225,9 @@ export declare class FlexTable extends LitElement {
213
225
  /** Calculate cumulative right offset for a right-pinned column */
214
226
  private _getPinnedRight;
215
227
  private _renderHeaderCell;
228
+ private _onHeaderContextMenu;
229
+ private _showHiddenBefore;
230
+ private _renderHeaderContextMenu;
216
231
  private _adjustFilterDropdown;
217
232
  private _onFilterBtnClick;
218
233
  private _renderFilterDropdown;
@@ -1,2 +1,2 @@
1
- import { a as e, i as t, n, r, t as i } from "./flex-table-DJvbRtBl.js";
1
+ import { a as e, i as t, n, r, t as i } from "./flex-table-zJST_6sy.js";
2
2
  export { i as FlexTable, t as RowSelectionState, r as UndoStack, n as exportData, e as renderCell };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { FlexTable } from './flex-table.js';
2
- export type { ColumnDefinition, ColumnType, DataRow, CellRenderer, CellEditor, CellValidator, SelectionMode, DataMode } from './models/types.js';
2
+ export type { ColumnDefinition, ColumnType, DataRow, CellRenderer, CellEditor, CellValidator, SelectionMode, DataMode, CellStyle, ConditionalRule } from './models/types.js';
3
3
  export type { CellPosition, CellRange } from './core/selection.js';
4
4
  export type { SortCriteria, SortDirection } from './core/sorting.js';
5
5
  export type { ColumnFilter, FilterPredicate, FilterErrorCallback } from './core/filtering.js';
@@ -65,6 +65,33 @@ export interface ColumnDefinition {
65
65
  validator?: CellValidator;
66
66
  /** Allowed values for select columns (strings or label/value pairs) */
67
67
  options?: string[] | SelectOption[];
68
+ /**
69
+ * Autocomplete for text editing.
70
+ * - true: show suggestions from existing column values
71
+ * - 'strict': same as true, but rejects values not in the list
72
+ */
73
+ autocomplete?: boolean | 'strict';
74
+ /**
75
+ * Display format for cell values. Applied during rendering; raw value is preserved.
76
+ * - String: number format pattern (e.g. '#,##0.00', '0.00%', '$#,##0') or date pattern (e.g. 'yyyy-MM-dd')
77
+ * - Function: custom formatter receiving (value, row, col)
78
+ */
79
+ format?: string | ((value: unknown, row: DataRow, col: ColumnDefinition) => string);
80
+ /** Per-column conditional formatting rules applied during cell rendering */
81
+ conditionalRules?: ConditionalRule[];
82
+ }
83
+ /** Style applied to a cell by a conditional formatting rule */
84
+ export interface CellStyle {
85
+ background?: string;
86
+ color?: string;
87
+ fontWeight?: 'bold' | 'normal';
88
+ fontStyle?: 'italic' | 'normal';
89
+ }
90
+ /** A single conditional formatting rule */
91
+ export interface ConditionalRule {
92
+ /** Returns true when this rule's style should be applied */
93
+ when: (value: unknown, row: DataRow, col: ColumnDefinition) => boolean;
94
+ style: CellStyle;
68
95
  }
69
96
  /**
70
97
  * A single data row — schema-agnostic key-value map.
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as e } from "./flex-table-DJvbRtBl.js";
1
+ import { t as e } from "./flex-table-zJST_6sy.js";
2
2
  import t from "react";
3
3
  import { createComponent as n } from "@lit/react";
4
4
  //#region src/react.ts
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Cell value formatting: number and date patterns.
3
+ * Patterns follow Excel-like conventions.
4
+ */
5
+ /**
6
+ * Apply a format string or function to a cell value.
7
+ * Returns empty string for null/undefined.
8
+ */
9
+ export declare function applyFormat(value: unknown, format: string | ((value: unknown) => string)): string;
10
+ /**
11
+ * Parse an Excel-style number format string and apply it.
12
+ * Supported patterns: '#,##0', '#,##0.00', '0.00%', '$#,##0.00', '#,##0.00%'
13
+ */
14
+ export declare function formatNumberValue(value: unknown, fmt: string): string;
15
+ /**
16
+ * Format a Date or ISO string value using a pattern.
17
+ * Tokens: yyyy, yy, MM, dd, HH, hh, mm, ss
18
+ */
19
+ export declare function formatDateValue(value: unknown, fmt: string): string;
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iyulab/flex-table",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "A minimalist, input-centric data grid web component",
5
5
  "type": "module",
6
6
  "main": "./dist/flex-table.js",