@iyulab/flex-table 0.41.1 → 0.42.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.42.0] - 2026-10-04
4
+
5
+ ### Added
6
+
7
+ - **`reveal: 'hover'` column option** — the column's content shows only while its row is hovered,
8
+ holds focus or is selected; for a row-actions column, so a list does not repeat the same buttons
9
+ on every row. The content stays focusable (Tab reveals it), touch screens always show it, and it
10
+ is not printed.
11
+
12
+ ### Changed
13
+
14
+ - **The virtual scroll renders only the rows it needs above the view.** The first row in view was
15
+ computed as if the header and the frozen rows scrolled away with the body, so up to
16
+ (header + frozen rows) ÷ row height extra rows were rendered above the view — ten with eight frozen
17
+ rows. What is visible does not change.
18
+
19
+ ## [0.41.2] - 2026-10-03
20
+
21
+ ### Fixed
22
+
23
+ - **The `datetime` cell editor shows and takes `YYYY-MM-DD HH:mm` in every browser language.** It was the
24
+ native `datetime-local` input, which follows the browser's UI language. It is now a text box in local
25
+ time that reads `2026-10-02 14:05` (`9:05`, a date alone is midnight), stores the local
26
+ `YYYY-MM-DDTHH:mm` string as before, and rejects text it cannot read with `validation-error`
27
+ ("Enter a date and time as YYYY-MM-DD HH:mm", new `flexTableLocale` key `notADateTime`).
28
+ - **Pasting `2026-10-02 14:05` into a datetime column stores `2026-10-02T14:05`.** Other text stays text.
29
+
30
+ ### Changed
31
+
32
+ - **The `@iyulab/components` peer is `>=1.56.0`** — date-times are read with its `parseDateTime`.
33
+
3
34
  ## [0.41.1] - 2026-10-03
4
35
 
5
36
  ### Fixed
package/README.md CHANGED
@@ -151,6 +151,7 @@ interface ColumnDefinition {
151
151
  validator?: CellValidator; // Validate before commit: (value, row, col) => string | null
152
152
  conditionalRules?: ConditionalRule[]; // Per-cell style rules, see below
153
153
  mergeRepeated?: boolean | ((row, previousRow, col) => boolean); // Merge runs of repeated values, see below
154
+ reveal?: 'always' | 'hover'; // 'hover': show only while the row is hovered, focused or selected (row actions)
154
155
  }
155
156
  ```
156
157
 
@@ -160,7 +161,7 @@ The `validator` callback returns `null` if valid, or an error message string. On
160
161
 
161
162
  A `number` column's built-in editor reads numbers the way people type them in the active `Locale` — `1,5` on a comma-decimal page is 1.5, `1.234,5` is 1234.5 — and shows the value with that locale's decimal separator. Text that is not a number is rejected the same way as a validator failure (`error` is the localized "Enter a number"). The number filter's conditions and pasted values are read the same way; pasted text that is not a number stays text.
162
163
 
163
- A `date` column's built-in editor is a text box that shows and takes `YYYY-MM-DD` in every browser language (the native date input would show the browser's UI language, e.g. `10/02/2026`). It also reads `2026/10/2`, `20261002` and `10-02` (this year), stores the ISO date string, and rejects text that is not a date (`error` is the localized "Enter a date as YYYY-MM-DD"). Pasted dates are read the same way.
164
+ A `date` column's built-in editor is a text box that shows and takes `YYYY-MM-DD` in every browser language (the native date input would show the browser's UI language, e.g. `10/02/2026`). It also reads `2026/10/2`, `20261002` and `10-02` (this year), stores the ISO date string, and rejects text that is not a date (`error` is the localized "Enter a date as YYYY-MM-DD"). Pasted dates are read the same way. A `datetime` column's editor works the same with a time: it shows `YYYY-MM-DD HH:mm` in local time, reads `2026-10-02 14:05` (a date alone is midnight), and stores the local `YYYY-MM-DDTHH:mm` string.
164
165
 
165
166
  ### `format` vs `render`
166
167
 
@@ -198,6 +199,18 @@ const columns: ColumnDefinition<Order>[] = [
198
199
 
199
200
  Rules are evaluated in order and combined; later matching rules override earlier ones for overlapping style properties.
200
201
 
202
+ ### Row actions that appear on hover
203
+
204
+ A column of row actions (edit, a "more" menu) repeated on every row turns a list into a wall of
205
+ buttons. `reveal: 'hover'` shows that column's content only while its row is hovered, holds focus or
206
+ is selected. The controls stay in the DOM and focusable — Tab reveals them — touch screens (no
207
+ hover) always show them, and they are not printed.
208
+
209
+ ```ts
210
+ { key: 'actions', label: '', width: 72, editable: false, reveal: 'hover',
211
+ render: (_, row) => html`<u-button size="sm" variant="ghost" aria-label="Edit">…</u-button>` }
212
+ ```
213
+
201
214
  ### Merging Repeated Values
202
215
 
203
216
  A list of child rows under a parent — order lines under an order, boxes under a shipment — repeats the parent's columns on every line. `mergeRepeated` draws each run of repeated values as one merged cell: the value shows once, on the run's first row, and the lines between the run's rows are dropped.
@@ -17,3 +17,9 @@ export declare function editableNumber(value: number): string;
17
17
  * negative offsets); a `Date` uses its local day. Anything else is shown as it is.
18
18
  */
19
19
  export declare function editableDate(value: unknown): string;
20
+ /**
21
+ * A `datetime` cell's value as `YYYY-MM-DD HH:mm` (local) for editing — the form the editor reads
22
+ * back. A local date-time string (`2026-10-02T14:05`, no offset) keeps its wall-clock time; a `Date`
23
+ * or a string with an offset (`Z`, `+09:00`) is shown in local time. Anything else is shown as it is.
24
+ */
25
+ export declare function editableDateTime(value: unknown): string;
@@ -2,11 +2,11 @@ import { n as e, t } from "./sorting-CjfjRxwL.js";
2
2
  import { LitElement as n, css as r, html as i, nothing as a } from "lit";
3
3
  import { customElement as o, property as s, state as c } from "lit/decorators.js";
4
4
  import { Locale as l } from "@iyulab/components/dist/utilities/Locale.js";
5
- import { dateTextPattern as u, formatDate as d, formatNumber as f, parseDate as p, parseNumber as m } from "@iyulab/components/dist/utilities/format.js";
6
- import { decodeTsv as h, encodeTsv as g } from "@iyulab/components/dist/utilities/tsv.js";
7
- import { isImeComposing as _ } from "@iyulab/components/dist/utilities/keyboard.js";
5
+ import { dateTextPattern as u, formatDate as d, formatNumber as f, parseDate as p, parseDateTime as m, parseNumber as h } from "@iyulab/components/dist/utilities/format.js";
6
+ import { decodeTsv as g, encodeTsv as _ } from "@iyulab/components/dist/utilities/tsv.js";
7
+ import { isImeComposing as v } from "@iyulab/components/dist/utilities/keyboard.js";
8
8
  //#region src/styles/flex-table.styles.ts
9
- var v = r`
9
+ var y = r`
10
10
  /* --- Theme Variables (Light defaults) --- */
11
11
  :host {
12
12
  --ft-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
@@ -918,16 +918,38 @@ var v = r`
918
918
  outline: 2px solid var(--ft-find-color);
919
919
  outline-offset: -2px;
920
920
  }
921
+
922
+ /* reveal: 'hover' — a row-actions column shows while the row is hovered, focused or selected.
923
+ opacity (not display/visibility) keeps the controls focusable, so Tab reveals them. */
924
+ .ft-cell.ft-reveal-hover > * {
925
+ opacity: 0;
926
+ transition: opacity var(--u-duration-fast, 140ms) ease-out;
927
+ }
928
+ .ft-row:hover .ft-cell.ft-reveal-hover > *,
929
+ .ft-row:focus-within .ft-cell.ft-reveal-hover > *,
930
+ .ft-row-selected .ft-cell.ft-reveal-hover > *,
931
+ .ft-cell.ft-reveal-hover.ft-active > * {
932
+ opacity: 1;
933
+ }
934
+ @media (hover: none) {
935
+ .ft-cell.ft-reveal-hover > * { opacity: 1; }
936
+ }
937
+ @media (prefers-reduced-motion: reduce) {
938
+ .ft-cell.ft-reveal-hover > * { transition: none; }
939
+ }
940
+ @media print {
941
+ .ft-cell.ft-reveal-hover > * { visibility: hidden; }
942
+ }
921
943
  `;
922
944
  //#endregion
923
945
  //#region src/renderers/format.ts
924
- function y(e, t) {
925
- return e == null ? "" : typeof t == "function" ? t(e) : b(t) ? S(e, t) : x(e, t);
946
+ function b(e, t) {
947
+ return e == null ? "" : typeof t == "function" ? t(e) : x(t) ? C(e, t) : S(e, t);
926
948
  }
927
- function b(e) {
949
+ function x(e) {
928
950
  return /[yMdHhms]/.test(e);
929
951
  }
930
- function x(e, t) {
952
+ function S(e, t) {
931
953
  let n = typeof e == "number" ? e : Number(e);
932
954
  if (isNaN(n)) return String(e);
933
955
  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, u = {
@@ -940,7 +962,7 @@ function x(e, t) {
940
962
  style: "percent"
941
963
  }).format(n) : (d = new Intl.NumberFormat(l.get(), u).format(n), a && (d = a + d)), d + "";
942
964
  }
943
- function S(e, t) {
965
+ function C(e, t) {
944
966
  let n;
945
967
  if (e instanceof Date) n = e;
946
968
  else if (typeof e == "string" || typeof e == "number") n = new Date(e);
@@ -951,26 +973,26 @@ function S(e, t) {
951
973
  }
952
974
  //#endregion
953
975
  //#region src/renderers/cell-renderer.ts
954
- function C(e, t, n) {
955
- return n.render ? n.render(e, t, n) : n.format ? typeof n.format == "function" ? n.format(e, t, n) : y(e, n.format) : w(e, n);
976
+ function w(e, t, n) {
977
+ return n.render ? n.render(e, t, n) : n.format ? typeof n.format == "function" ? n.format(e, t, n) : b(e, n.format) : T(e, n);
956
978
  }
957
- function w(e, t) {
979
+ function T(e, t) {
958
980
  if (e == null) return "";
959
981
  switch (t.type) {
960
- case "number": return D(e);
982
+ case "number": return O(e);
961
983
  case "boolean": return i`<span class="ft-bool">${e ? "✔" : ""}</span>`;
962
- case "date": return O(e);
963
- case "datetime": return ee(e);
964
- case "select": return T(e, t);
984
+ case "date": return ee(e);
985
+ case "datetime": return te(e);
986
+ case "select": return E(e, t);
965
987
  default: return String(e);
966
988
  }
967
989
  }
968
- function T(e, t) {
990
+ function E(e, t) {
969
991
  if (!t.options || t.options.length === 0 || typeof t.options[0] == "string") return e == null ? "" : String(e);
970
992
  let n = t.options.find((t) => t.value === e);
971
993
  return n ? n.label : e == null ? "" : String(e);
972
994
  }
973
- var E = {
995
+ var D = {
974
996
  year: "numeric",
975
997
  month: "numeric",
976
998
  day: "numeric",
@@ -978,14 +1000,14 @@ var E = {
978
1000
  minute: "numeric",
979
1001
  second: "numeric"
980
1002
  };
981
- function D(e) {
1003
+ function O(e) {
982
1004
  return typeof e == "number" ? Number.isInteger(e) ? f(e) : f(e, { minimumFractionDigits: 1 }) : String(e);
983
1005
  }
984
- function O(e) {
1006
+ function ee(e) {
985
1007
  return e instanceof Date || typeof e == "string" ? d(e) : String(e);
986
1008
  }
987
- function ee(e) {
988
- return e instanceof Date || typeof e == "string" ? d(e, E) : String(e);
1009
+ function te(e) {
1010
+ return e instanceof Date || typeof e == "string" ? d(e, D) : String(e);
989
1011
  }
990
1012
  //#endregion
991
1013
  //#region src/locale.ts
@@ -1045,6 +1067,7 @@ k.register("en", {
1045
1067
  dropFileToImport: "Drop file to import (.xlsx / .csv)",
1046
1068
  notANumber: "Enter a number",
1047
1069
  notADate: "Enter a date as YYYY-MM-DD",
1070
+ notADateTime: "Enter a date and time as YYYY-MM-DD HH:mm",
1048
1071
  notInList: "Value must be from the existing list"
1049
1072
  }), k.register("ko", {
1050
1073
  showHiddenColumns: "숨긴 열 보기",
@@ -1101,12 +1124,13 @@ k.register("en", {
1101
1124
  dropFileToImport: "가져올 파일을 놓으세요 (.xlsx / .csv)",
1102
1125
  notANumber: "숫자를 입력하세요",
1103
1126
  notADate: "날짜를 YYYY-MM-DD 로 입력하세요",
1127
+ notADateTime: "날짜와 시간을 YYYY-MM-DD HH:mm 으로 입력하세요",
1104
1128
  notInList: "목록에 있는 값이어야 합니다"
1105
1129
  });
1106
1130
  var A = (e, t) => k.text(e, t);
1107
1131
  //#endregion
1108
1132
  //#region src/core/selection.ts
1109
- function te(e) {
1133
+ function ne(e) {
1110
1134
  return {
1111
1135
  startRow: Math.min(e.startRow, e.endRow),
1112
1136
  startCol: Math.min(e.startCol, e.endCol),
@@ -1114,7 +1138,7 @@ function te(e) {
1114
1138
  endCol: Math.max(e.startCol, e.endCol)
1115
1139
  };
1116
1140
  }
1117
- var ne = class {
1141
+ var re = class {
1118
1142
  constructor() {
1119
1143
  this.activeCell = null, this.rangeAnchor = null, this.range = null, this.extraRanges = [], this._rowCount = 0, this._colCount = 0;
1120
1144
  }
@@ -1141,7 +1165,7 @@ var ne = class {
1141
1165
  return e < 0 || e >= this._rowCount || t < 0 || t >= this._colCount ? this.activeCell : (!this.rangeAnchor && this.activeCell && (this.rangeAnchor = { ...this.activeCell }), this.activeCell = {
1142
1166
  row: e,
1143
1167
  col: t
1144
- }, this.rangeAnchor && (this.range = te({
1168
+ }, this.rangeAnchor && (this.range = ne({
1145
1169
  startRow: this.rangeAnchor.row,
1146
1170
  startCol: this.rangeAnchor.col,
1147
1171
  endRow: e,
@@ -1212,7 +1236,7 @@ var ne = class {
1212
1236
  shiftMoveRight() {
1213
1237
  return this.activeCell ? this.setActiveWithRange(this.activeCell.row, this.activeCell.col + 1) : null;
1214
1238
  }
1215
- }, re = class {
1239
+ }, ie = class {
1216
1240
  constructor() {
1217
1241
  this.current = null;
1218
1242
  }
@@ -1294,7 +1318,7 @@ var ne = class {
1294
1318
  };
1295
1319
  //#endregion
1296
1320
  //#region src/core/filtering.ts
1297
- function ie(e, t, n) {
1321
+ function ae(e, t, n) {
1298
1322
  if (t.length === 0) return Array.from({ length: e.length }, (e, t) => t);
1299
1323
  let r = [];
1300
1324
  for (let i = 0; i < e.length; i++) {
@@ -1313,9 +1337,9 @@ function ie(e, t, n) {
1313
1337
  }
1314
1338
  //#endregion
1315
1339
  //#region src/core/undo.ts
1316
- var ae = 100, M = class {
1340
+ var oe = 100, M = class {
1317
1341
  constructor() {
1318
- this._undoStack = [], this._redoStack = [], this._maxSize = ae;
1342
+ this._undoStack = [], this._redoStack = [], this._maxSize = oe;
1319
1343
  }
1320
1344
  get maxSize() {
1321
1345
  return this._maxSize;
@@ -1352,7 +1376,7 @@ var ae = 100, M = class {
1352
1376
  };
1353
1377
  //#endregion
1354
1378
  //#region src/clipboard/clipboard.ts
1355
- function oe(e, t, n) {
1379
+ function se(e, t, n) {
1356
1380
  let r = [];
1357
1381
  for (let i = n.startRow; i <= n.endRow; i++) {
1358
1382
  let a = e[i], o = [];
@@ -1362,19 +1386,20 @@ function oe(e, t, n) {
1362
1386
  }
1363
1387
  r.push(o);
1364
1388
  }
1365
- return g(r);
1389
+ return _(r);
1366
1390
  }
1367
1391
  function N(e, t) {
1368
1392
  if (e === "") return null;
1369
1393
  switch (t.type) {
1370
1394
  case "number": {
1371
- let t = m(e);
1395
+ let t = h(e);
1372
1396
  if (t !== null) return t;
1373
1397
  let n = Number(e.trim());
1374
1398
  return e.trim() !== "" && Number.isFinite(n) ? n : e;
1375
1399
  }
1376
1400
  case "boolean": return e.toLowerCase() === "true" || e === "1";
1377
1401
  case "date": return p(e) ?? e;
1402
+ case "datetime": return m(e) ?? e;
1378
1403
  default: return e;
1379
1404
  }
1380
1405
  }
@@ -1384,15 +1409,24 @@ function P(e) {
1384
1409
  let n = new Intl.NumberFormat(l.get()).formatToParts(1.5).find((e) => e.type === "decimal")?.value;
1385
1410
  return n && n !== "." ? t.replace(".", n) : t;
1386
1411
  }
1387
- function se(e) {
1412
+ function ce(e) {
1388
1413
  if (e == null || e === "") return "";
1389
1414
  if (e instanceof Date) return Number.isNaN(e.getTime()) ? "" : `${e.getFullYear()}-${String(e.getMonth() + 1).padStart(2, "0")}-${String(e.getDate()).padStart(2, "0")}`;
1390
1415
  let t = String(e), n = /^(\d{4}-\d{2}-\d{2})(?:$|T)/.exec(t);
1391
1416
  return n ? n[1] : t;
1392
1417
  }
1418
+ function le(e) {
1419
+ if (e == null || e === "") return "";
1420
+ let t = e instanceof Date ? "" : String(e), n = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2})(?::\d{2}(?:\.\d+)?)?$/.exec(t);
1421
+ if (n) return `${n[1]} ${n[2]}`;
1422
+ let r = e instanceof Date ? e : /^\d{4}-\d{2}-\d{2}T/.test(t) ? new Date(t) : null;
1423
+ if (!r || Number.isNaN(r.getTime())) return t;
1424
+ let i = (e) => String(e).padStart(2, "0");
1425
+ return `${r.getFullYear()}-${i(r.getMonth() + 1)}-${i(r.getDate())} ${i(r.getHours())}:${i(r.getMinutes())}`;
1426
+ }
1393
1427
  //#endregion
1394
1428
  //#region src/export/xlsx-writer.ts
1395
- var ce = (() => {
1429
+ var ue = (() => {
1396
1430
  let e = /* @__PURE__ */ new Uint32Array(256);
1397
1431
  for (let t = 0; t < 256; t++) {
1398
1432
  let n = t;
@@ -1401,16 +1435,16 @@ var ce = (() => {
1401
1435
  }
1402
1436
  return e;
1403
1437
  })();
1404
- function le(e) {
1438
+ function de(e) {
1405
1439
  let t = 4294967295;
1406
- for (let n = 0; n < e.length; n++) t = ce[(t ^ e[n]) & 255] ^ t >>> 8;
1440
+ for (let n = 0; n < e.length; n++) t = ue[(t ^ e[n]) & 255] ^ t >>> 8;
1407
1441
  return (t ^ 4294967295) >>> 0;
1408
1442
  }
1409
1443
  var F = new TextEncoder();
1410
- function ue(e) {
1444
+ function fe(e) {
1411
1445
  let t = [], n = [], r = 0;
1412
1446
  for (let i of e) {
1413
- let e = F.encode(i.name), a = le(i.data), o = i.data.length, s = new ArrayBuffer(30 + e.length), c = new DataView(s);
1447
+ let e = F.encode(i.name), a = de(i.data), o = i.data.length, s = new ArrayBuffer(30 + e.length), c = new DataView(s);
1414
1448
  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);
1415
1449
  let l = new ArrayBuffer(46 + e.length), u = new DataView(l);
1416
1450
  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;
@@ -1430,27 +1464,27 @@ function I(e) {
1430
1464
  function L(e) {
1431
1465
  return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1432
1466
  }
1433
- function de(e) {
1467
+ function pe(e) {
1434
1468
  let t = "";
1435
1469
  for (e++; e > 0;) t = String.fromCharCode(64 + (e % 26 || 26)) + t, e = Math.floor((e - 1) / 26);
1436
1470
  return t;
1437
1471
  }
1438
1472
  function R(e, t) {
1439
- return de(t) + (e + 1);
1473
+ return pe(t) + (e + 1);
1440
1474
  }
1441
- function fe(e) {
1475
+ function me(e) {
1442
1476
  let t = Date.UTC(1899, 11, 30);
1443
1477
  return (e.getTime() - t) / 864e5;
1444
1478
  }
1445
- var pe = "<?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>", me = "<?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>", z = "<?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>", he = "<?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>", ge = "<?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>";
1446
- function _e(e, t) {
1479
+ var z = "<?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>", he = "<?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>", ge = "<?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>", _e = "<?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>", ve = "<?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>";
1480
+ function ye(e, t) {
1447
1481
  let n = [], r = t.map((e, t) => `<c r="${R(0, t)}" t="str" s="2"><v>${L(e.label)}</v></c>`);
1448
1482
  return n.push(`<row r="1">${r.join("")}</row>`), e.forEach((e, r) => {
1449
1483
  let i = t.map((t, n) => {
1450
1484
  let i = R(r + 1, n), a = e[t.key];
1451
1485
  if (a == null) return `<c r="${i}" t="str"><v></v></c>`;
1452
1486
  let o = t.type ?? "text";
1453
- 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>${fe(a).toFixed(6)}</v></c>` : o === "boolean" ? `<c r="${i}" t="b"><v>${+!!a}</v></c>` : `<c r="${i}" t="str"><v>${L(String(a))}</v></c>`;
1487
+ 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>${me(a).toFixed(6)}</v></c>` : o === "boolean" ? `<c r="${i}" t="b"><v>${+!!a}</v></c>` : `<c r="${i}" t="str"><v>${L(String(a))}</v></c>`;
1454
1488
  });
1455
1489
  n.push(`<row r="${r + 2}">${i.join("")}</row>`);
1456
1490
  }), `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
@@ -1458,24 +1492,24 @@ function _e(e, t) {
1458
1492
  <sheetData>${n.join("")}</sheetData>
1459
1493
  </worksheet>`;
1460
1494
  }
1461
- function ve(e, t) {
1462
- let n = _e(e, t);
1463
- return ue([
1495
+ function be(e, t) {
1496
+ let n = ye(e, t);
1497
+ return fe([
1464
1498
  {
1465
1499
  name: "[Content_Types].xml",
1466
- data: F.encode(pe)
1500
+ data: F.encode(z)
1467
1501
  },
1468
1502
  {
1469
1503
  name: "_rels/.rels",
1470
- data: F.encode(me)
1504
+ data: F.encode(he)
1471
1505
  },
1472
1506
  {
1473
1507
  name: "xl/workbook.xml",
1474
- data: F.encode(z)
1508
+ data: F.encode(ge)
1475
1509
  },
1476
1510
  {
1477
1511
  name: "xl/_rels/workbook.xml.rels",
1478
- data: F.encode(he)
1512
+ data: F.encode(_e)
1479
1513
  },
1480
1514
  {
1481
1515
  name: "xl/worksheets/sheet1.xml",
@@ -1483,7 +1517,7 @@ function ve(e, t) {
1483
1517
  },
1484
1518
  {
1485
1519
  name: "xl/styles.xml",
1486
- data: F.encode(ge)
1520
+ data: F.encode(ve)
1487
1521
  }
1488
1522
  ]);
1489
1523
  }
@@ -1493,20 +1527,20 @@ function B(e, t, n) {
1493
1527
  switch (n) {
1494
1528
  case "csv": return V(e, t, ",");
1495
1529
  case "tsv": return V(e, t, " ");
1496
- case "json": return be(e, t);
1497
- case "xlsx": return ve(e, t);
1530
+ case "json": return Se(e, t);
1531
+ case "xlsx": return be(e, t);
1498
1532
  }
1499
1533
  }
1500
- function ye(e, t) {
1534
+ function xe(e, t) {
1501
1535
  return e == null ? "" : (t.type === "date" || t.type === "datetime") && e instanceof Date ? e.toISOString() : String(e);
1502
1536
  }
1503
1537
  function V(e, t, n) {
1504
- return [t.map((e) => H(e.label, n)).join(n), ...e.map((e) => t.map((t) => H(ye(e[t.key], t), n)).join(n))].join("\n");
1538
+ return [t.map((e) => H(e.label, n)).join(n), ...e.map((e) => t.map((t) => H(xe(e[t.key], t), n)).join(n))].join("\n");
1505
1539
  }
1506
1540
  function H(e, t) {
1507
1541
  return e.includes(t) || e.includes("\"") || e.includes("\n") || e.includes("\r") ? "\"" + e.replace(/"/g, "\"\"") + "\"" : e;
1508
1542
  }
1509
- function be(e, t) {
1543
+ function Se(e, t) {
1510
1544
  let n = e.map((e) => {
1511
1545
  let n = {};
1512
1546
  for (let r of t) {
@@ -1517,35 +1551,35 @@ function be(e, t) {
1517
1551
  });
1518
1552
  return JSON.stringify(n, null, 2);
1519
1553
  }
1520
- function xe(e, t, n) {
1554
+ function Ce(e, t, n) {
1521
1555
  let r = new Blob([e], { type: n }), i = URL.createObjectURL(r), a = document.createElement("a");
1522
1556
  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);
1523
1557
  }
1524
- var Se = {
1558
+ var U = {
1525
1559
  csv: "text/csv;charset=utf-8",
1526
1560
  tsv: "text/tab-separated-values;charset=utf-8",
1527
1561
  json: "application/json;charset=utf-8",
1528
1562
  xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
1529
- }, Ce = {
1563
+ }, we = {
1530
1564
  csv: ".csv",
1531
1565
  tsv: ".tsv",
1532
1566
  json: ".json",
1533
1567
  xlsx: ".xlsx"
1534
1568
  };
1535
- function we(e) {
1536
- return Se[e];
1537
- }
1538
1569
  function Te(e) {
1539
- return Ce[e];
1570
+ return U[e];
1571
+ }
1572
+ function Ee(e) {
1573
+ return we[e];
1540
1574
  }
1541
1575
  //#endregion
1542
1576
  //#region src/export/xlsx-reader.ts
1543
- function Ee(e) {
1577
+ function De(e) {
1544
1578
  for (let t = e.byteLength - 22; t >= 0; t--) if (e.getUint32(t, !0) === 101010256) return t;
1545
1579
  throw Error("XLSX: End of central directory not found — not a valid ZIP file");
1546
1580
  }
1547
- function De(e) {
1548
- let t = Ee(e), n = e.getUint32(t + 16, !0), r = e.getUint32(t + 12, !0), i = new TextDecoder("utf-8"), a = [], o = n, s = n + r;
1581
+ function Oe(e) {
1582
+ let t = De(e), n = e.getUint32(t + 16, !0), r = e.getUint32(t + 12, !0), i = new TextDecoder("utf-8"), a = [], o = n, s = n + r;
1549
1583
  for (; o < s && e.getUint32(o, !0) === 33639248;) {
1550
1584
  let t = e.getUint16(o + 10, !0), n = e.getUint32(o + 20, !0), r = e.getUint32(o + 24, !0), s = e.getUint16(o + 28, !0), c = e.getUint16(o + 30, !0), l = e.getUint16(o + 32, !0), u = e.getUint32(o + 42, !0), d = i.decode(new Uint8Array(e.buffer, o + 46, s));
1551
1585
  a.push({
@@ -1558,7 +1592,7 @@ function De(e) {
1558
1592
  }
1559
1593
  return a;
1560
1594
  }
1561
- async function Oe(e) {
1595
+ async function ke(e) {
1562
1596
  let t = new DecompressionStream("deflate-raw"), n = t.writable.getWriter(), r = t.readable.getReader();
1563
1597
  n.write(e), n.close();
1564
1598
  let i = [];
@@ -1571,28 +1605,28 @@ async function Oe(e) {
1571
1605
  for (let e of i) o.set(e, s), s += e.length;
1572
1606
  return o;
1573
1607
  }
1574
- async function ke(e, t) {
1608
+ async function Ae(e, t) {
1575
1609
  let n = t.localHeaderOffset, r = e.getUint16(n + 26, !0), i = e.getUint16(n + 28, !0), a = n + 30 + r + i, o = new Uint8Array(e.buffer, a, t.compressedSize);
1576
1610
  if (t.compressionMethod === 0) return o.slice();
1577
- if (t.compressionMethod === 8) return Oe(o);
1611
+ if (t.compressionMethod === 8) return ke(o);
1578
1612
  throw Error(`XLSX: Unsupported compression method ${t.compressionMethod}`);
1579
1613
  }
1580
- async function Ae(e) {
1581
- let t = new DataView(e), n = De(t), r = /* @__PURE__ */ new Map();
1582
- for (let e of n) e.compressedSize === 0 && e.name.endsWith("/") || r.set(e.name, await ke(t, e));
1614
+ async function je(e) {
1615
+ let t = new DataView(e), n = Oe(t), r = /* @__PURE__ */ new Map();
1616
+ for (let e of n) e.compressedSize === 0 && e.name.endsWith("/") || r.set(e.name, await Ae(t, e));
1583
1617
  return r;
1584
1618
  }
1585
- var je = new TextDecoder("utf-8");
1586
- function U(e) {
1587
- let t = je.decode(e);
1619
+ var Me = new TextDecoder("utf-8");
1620
+ function W(e) {
1621
+ let t = Me.decode(e);
1588
1622
  return new DOMParser().parseFromString(t, "application/xml");
1589
1623
  }
1590
- function Me(e) {
1624
+ function Ne(e) {
1591
1625
  if (!e) return [];
1592
- let t = U(e);
1626
+ let t = W(e);
1593
1627
  return Array.from(t.querySelectorAll("si")).map((e) => Array.from(e.querySelectorAll("t")).map((e) => e.textContent ?? "").join(""));
1594
1628
  }
1595
- var Ne = /* @__PURE__ */ new Set([
1629
+ var Pe = /* @__PURE__ */ new Set([
1596
1630
  14,
1597
1631
  15,
1598
1632
  16,
@@ -1612,37 +1646,37 @@ var Ne = /* @__PURE__ */ new Set([
1612
1646
  57,
1613
1647
  58
1614
1648
  ]);
1615
- function Pe(e) {
1649
+ function Fe(e) {
1616
1650
  let t = e.replace(/"[^"]*"/g, "").replace(/\\./g, "").replace(/\[[^\]]*\]/g, "").split(";")[0];
1617
1651
  return /[dy]/i.test(t);
1618
1652
  }
1619
- function Fe(e) {
1653
+ function Ie(e) {
1620
1654
  let t = /* @__PURE__ */ new Set();
1621
1655
  if (!e) return t;
1622
- let n = U(e), r = /* @__PURE__ */ new Map();
1656
+ let n = W(e), r = /* @__PURE__ */ new Map();
1623
1657
  for (let e of Array.from(n.querySelectorAll("numFmts > numFmt"))) r.set(parseInt(e.getAttribute("numFmtId") ?? "", 10), e.getAttribute("formatCode") ?? "");
1624
1658
  return Array.from(n.querySelectorAll("cellXfs > xf")).forEach((e, n) => {
1625
1659
  let i = parseInt(e.getAttribute("numFmtId") ?? "0", 10), a = r.get(i);
1626
- (a === void 0 ? Ne.has(i) : Pe(a)) && t.add(n);
1660
+ (a === void 0 ? Pe.has(i) : Fe(a)) && t.add(n);
1627
1661
  }), t;
1628
1662
  }
1629
- function Ie(e) {
1663
+ function Le(e) {
1630
1664
  if (!e) return !1;
1631
- let t = U(e).querySelector("workbookPr")?.getAttribute("date1904");
1665
+ let t = W(e).querySelector("workbookPr")?.getAttribute("date1904");
1632
1666
  return t === "1" || t === "true";
1633
1667
  }
1634
- function W(e, t) {
1668
+ function Re(e, t) {
1635
1669
  return new Date((t ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30)) + Math.floor(e) * 864e5).toISOString().slice(0, 10);
1636
1670
  }
1637
- function Le(e, t, n, r) {
1638
- let i = U(e).querySelectorAll("row");
1671
+ function ze(e, t, n, r) {
1672
+ let i = W(e).querySelectorAll("row");
1639
1673
  if (i.length === 0) return [];
1640
1674
  let a = 0, o = /* @__PURE__ */ new Map();
1641
1675
  for (let e of i) {
1642
1676
  let i = parseInt(e.getAttribute("r") ?? "1", 10) - 1, s = /* @__PURE__ */ new Map();
1643
1677
  o.set(i, s);
1644
1678
  for (let i of e.querySelectorAll("c")) {
1645
- let e = Re(i.getAttribute("r") ?? "");
1679
+ let e = Be(i.getAttribute("r") ?? "");
1646
1680
  e > a && (a = e);
1647
1681
  let o = i.getAttribute("t") ?? "", c = i.getAttribute("s") ?? "", l = i.querySelector("v")?.textContent ?? "", u = i.querySelector("is > t")?.textContent ?? null, d;
1648
1682
  if (o === "s") d = t[parseInt(l, 10)] ?? "";
@@ -1651,7 +1685,7 @@ function Le(e, t, n, r) {
1651
1685
  else if (l === "") d = "";
1652
1686
  else {
1653
1687
  let e = parseFloat(l);
1654
- d = c !== "" && n.has(parseInt(c, 10)) && !isNaN(e) && e >= 0 ? W(e, r) : l;
1688
+ d = c !== "" && n.has(parseInt(c, 10)) && !isNaN(e) && e >= 0 ? Re(e, r) : l;
1655
1689
  }
1656
1690
  s.set(e, d);
1657
1691
  }
@@ -1665,13 +1699,13 @@ function Le(e, t, n, r) {
1665
1699
  }
1666
1700
  return c;
1667
1701
  }
1668
- function Re(e) {
1702
+ function Be(e) {
1669
1703
  let t = 0, n = 0;
1670
1704
  for (; n < e.length && e[n] >= "A" && e[n] <= "Z";) t = t * 26 + (e.charCodeAt(n) - 64), n++;
1671
1705
  return t - 1;
1672
1706
  }
1673
- async function ze(e) {
1674
- let t = await Ae(e), n = Me(t.get("xl/sharedStrings.xml")), r = t.get("xl/worksheets/sheet1.xml");
1707
+ async function Ve(e) {
1708
+ let t = await je(e), n = Ne(t.get("xl/sharedStrings.xml")), r = t.get("xl/worksheets/sheet1.xml");
1675
1709
  if (!r) {
1676
1710
  for (let [e, n] of t) if (e.toLowerCase().includes("worksheets/sheet")) {
1677
1711
  r = n;
@@ -1679,7 +1713,7 @@ async function ze(e) {
1679
1713
  }
1680
1714
  }
1681
1715
  if (!r) throw Error("XLSX: No worksheet found");
1682
- let i = Fe(t.get("xl/styles.xml")), a = Ie(t.get("xl/workbook.xml")), o = Le(r, n, i, a);
1716
+ let i = Ie(t.get("xl/styles.xml")), a = Le(t.get("xl/workbook.xml")), o = ze(r, n, i, a);
1683
1717
  return o.length === 0 ? {
1684
1718
  headers: [],
1685
1719
  rows: []
@@ -1710,14 +1744,14 @@ var q = {
1710
1744
  lt: "<",
1711
1745
  gte: "≥",
1712
1746
  lte: "≤"
1713
- }, J = 120, Y = 40, X = 20, Z = 32, Q = 5, Be = 200, $ = class extends n {
1747
+ }, J = 120, Y = 40, X = 20, Z = 32, Q = 5, He = 200, $ = class extends n {
1714
1748
  constructor(...e) {
1715
- super(...e), this.columns = [], this._data = [], this._inUndoRedo = !1, this.clearUndoOnDataChange = !1, this.clearSelectionOnDataChange = !1, this._rowHeightFromCss = Z, this.showRowNumbers = !1, this.theme = void 0, this.maxRows = 0, this.editable = !0, this.showFilters = !1, this.showContextMenu = !1, this.frozenRows = 0, this.emptyMessage = "No data", this.noMatchingMessage = "No matching data", this.loading = !1, this.importEnabled = !1, this.selectable = !1, this.dataMode = "client", this.footerData = null, this.stylesheets = [], this._scrollTop = 0, this._scrollLeft = 0, this._viewportHeight = 0, this._viewportWidth = 0, this._colLeftOffsets = [], this._totalRowWidth = 0, this._baseStyleSheets = [], this._activeCell = null, this._editingCell = null, this._sortCriteria = [], this._selection = new ne(), this._editing = new re(), this._rowSelection = new j(), this._undo = new M(), this._filters = [], this._filteredIndices = [], this._sortedIndices = [], this._openFilterKey = null, this._rowSelectionVersion = 0, this._lastCheckboxRowIndex = null, this._checkboxShiftPending = !1, this._autocompleteState = null, this._headerMenu = null, this._bodyContextMenu = null, this._commentPopup = null, this._viewDirty = !0, this._isDragging = !1, this._wasDrag = !1, this._resizing = null, this._resizeCleanup = null, this._wasHeaderDrag = !1, this._wasRowDrag = !1, 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._comments = /* @__PURE__ */ new Map(), this._isDragOver = !1, this._warnedUnconstrained = !1, this._onCommentPopupOutsideClick = () => {
1749
+ super(...e), this.columns = [], this._data = [], this._inUndoRedo = !1, this.clearUndoOnDataChange = !1, this.clearSelectionOnDataChange = !1, this._rowHeightFromCss = Z, this.showRowNumbers = !1, this.theme = void 0, this.maxRows = 0, this.editable = !0, this.showFilters = !1, this.showContextMenu = !1, this.frozenRows = 0, this.emptyMessage = "No data", this.noMatchingMessage = "No matching data", this.loading = !1, this.importEnabled = !1, this.selectable = !1, this.dataMode = "client", this.footerData = null, this.stylesheets = [], this._scrollTop = 0, this._scrollLeft = 0, this._viewportHeight = 0, this._viewportWidth = 0, this._colLeftOffsets = [], this._totalRowWidth = 0, this._baseStyleSheets = [], this._activeCell = null, this._editingCell = null, this._sortCriteria = [], this._selection = new re(), this._editing = new ie(), this._rowSelection = new j(), this._undo = new M(), this._filters = [], this._filteredIndices = [], this._sortedIndices = [], this._openFilterKey = null, this._rowSelectionVersion = 0, this._lastCheckboxRowIndex = null, this._checkboxShiftPending = !1, this._autocompleteState = null, this._headerMenu = null, this._bodyContextMenu = null, this._commentPopup = null, this._viewDirty = !0, this._isDragging = !1, this._wasDrag = !1, this._resizing = null, this._resizeCleanup = null, this._wasHeaderDrag = !1, this._wasRowDrag = !1, 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._comments = /* @__PURE__ */ new Map(), this._isDragOver = !1, this._warnedUnconstrained = !1, this._onCommentPopupOutsideClick = () => {
1716
1750
  this._commitCommentPopup();
1717
1751
  }, this._invalidCells = /* @__PURE__ */ new Map(), this._textFilterState = /* @__PURE__ */ new Map(), this._numberFilterState = /* @__PURE__ */ new Map(), this._dateFilterState = /* @__PURE__ */ new Map(), this._emptyFilterState = /* @__PURE__ */ new Map();
1718
1752
  }
1719
1753
  static {
1720
- this.styles = v;
1754
+ this.styles = y;
1721
1755
  }
1722
1756
  get data() {
1723
1757
  return this._data;
@@ -2082,7 +2116,7 @@ var q = {
2082
2116
  return B(this._sortedIndices.map((e) => this.data[e]), this.visibleColumns, e);
2083
2117
  }
2084
2118
  exportToFile(e, t) {
2085
- xe(this.exportToString(e), t ?? `export${Te(e)}`, we(e));
2119
+ Ce(this.exportToString(e), t ?? `export${Ee(e)}`, Te(e));
2086
2120
  }
2087
2121
  setComment(e, t, n) {
2088
2122
  let r = this.getComment(e, t);
@@ -2139,10 +2173,10 @@ var q = {
2139
2173
  async importFromFile(e) {
2140
2174
  let t = e.name.toLowerCase();
2141
2175
  if (t.endsWith(".xlsx")) {
2142
- let t = await ze(await e.arrayBuffer());
2176
+ let t = await Ve(await e.arrayBuffer());
2143
2177
  this._applyImportedSheet(t);
2144
2178
  } else if (t.endsWith(".csv") || t.endsWith(".tsv")) {
2145
- let t = await e.text(), n = h(t);
2179
+ let t = await e.text(), n = g(t);
2146
2180
  this._applyImportedRows(n);
2147
2181
  } else console.warn(`flex-table importFromFile: unsupported file type "${e.name}"`);
2148
2182
  }
@@ -2258,10 +2292,10 @@ var q = {
2258
2292
  return Math.max(0, this._visibleRowCount - this._frozenRowCount) * this.rowHeight;
2259
2293
  }
2260
2294
  get visibleRange() {
2261
- let e = this._frozenRowCount, t = Math.max(0, this._scrollTop - this.headerHeight - this.frozenRowsHeight), n = e + Math.floor(t / this.rowHeight), r = Math.max(e, n - Q), i = Math.ceil(this._viewportHeight / this.rowHeight);
2295
+ let e = this._frozenRowCount, t = e + Math.floor(Math.max(0, this._scrollTop) / this.rowHeight), n = Math.max(e, t - Q), r = Math.max(0, this._viewportHeight - this.headerHeight - this.frozenRowsHeight), i = Math.ceil(r / this.rowHeight) + 1;
2262
2296
  return {
2263
- start: r,
2264
- end: Math.min(this._visibleRowCount, n + i + Q)
2297
+ start: n,
2298
+ end: Math.min(this._visibleRowCount, t + i + Q)
2265
2299
  };
2266
2300
  }
2267
2301
  connectedCallback() {
@@ -2332,7 +2366,7 @@ var q = {
2332
2366
  this._filteredIndices = Array.from({ length: this.data.length }, (e, t) => t), this._sortedIndices = this._filteredIndices;
2333
2367
  return;
2334
2368
  }
2335
- if (this._filteredIndices = ie(this.data, this._filters, (e, t, n) => {
2369
+ if (this._filteredIndices = ae(this.data, this._filters, (e, t, n) => {
2336
2370
  this.dispatchEvent(new CustomEvent("filter-error", {
2337
2371
  detail: {
2338
2372
  error: e,
@@ -2362,7 +2396,7 @@ var q = {
2362
2396
  e !== this._viewportHeight && (this._viewportHeight = e), t !== this._viewportWidth && (this._viewportWidth = t), this._warnIfUnconstrained();
2363
2397
  }
2364
2398
  _warnIfUnconstrained() {
2365
- this._warnedUnconstrained || process.env.NODE_ENV === "production" || this._visibleRowCount < Be || this.scrollHeight > this.clientHeight + 1 || (this._warnedUnconstrained = !0, console.warn(`[@iyulab/flex-table] ${this._visibleRowCount} rows but the host has no height constraint — it grew to fit every row, so virtual scrolling is off and all rows are rendered. Give the host a height (e.g. height: 400px, or flex: 1 1 auto with min-height: 0 inside a sized parent) so it becomes the scroll container.`));
2399
+ this._warnedUnconstrained || process.env.NODE_ENV === "production" || this._visibleRowCount < He || this.scrollHeight > this.clientHeight + 1 || (this._warnedUnconstrained = !0, console.warn(`[@iyulab/flex-table] ${this._visibleRowCount} rows but the host has no height constraint — it grew to fit every row, so virtual scrolling is off and all rows are rendered. Give the host a height (e.g. height: 400px, or flex: 1 1 auto with min-height: 0 inside a sized parent) so it becomes the scroll container.`));
2366
2400
  }
2367
2401
  _onScroll() {
2368
2402
  this._scrollTop = this.scrollTop, this._scrollLeft = this.scrollLeft;
@@ -2516,8 +2550,8 @@ var q = {
2516
2550
  }));
2517
2551
  return;
2518
2552
  }
2519
- if (a.type === "number" && typeof e == "string" || a.type === "date" && typeof e == "string" && p(e) === null) {
2520
- let t = A(a.type === "number" ? "notANumber" : "notADate");
2553
+ if (a.type === "number" && typeof e == "string" || a.type === "date" && typeof e == "string" && p(e) === null || a.type === "datetime" && typeof e == "string" && m(e) === null) {
2554
+ let t = A(a.type === "number" ? "notANumber" : a.type === "date" ? "notADate" : "notADateTime");
2521
2555
  this._markCellInvalid(n, r, t), this.dispatchEvent(new CustomEvent("validation-error", {
2522
2556
  detail: {
2523
2557
  row: i,
@@ -2581,7 +2615,7 @@ var q = {
2581
2615
  }));
2582
2616
  }
2583
2617
  _onEditorKeyDown(e) {
2584
- if (!_(e)) {
2618
+ if (!v(e)) {
2585
2619
  if (this._autocompleteState && this._autocompleteState.candidates.length > 0) {
2586
2620
  if (e.key === "ArrowDown") {
2587
2621
  e.preventDefault(), e.stopPropagation();
@@ -2649,7 +2683,7 @@ var q = {
2649
2683
  ].includes(t) && this._handleCtrlKey(e)) return;
2650
2684
  }
2651
2685
  if (t.length !== 0 && this._visibleRowCount !== 0) {
2652
- if ((e.key === "Enter" || e.key === "F2") && this._activeCell && !_(e)) {
2686
+ if ((e.key === "Enter" || e.key === "F2") && this._activeCell && !v(e)) {
2653
2687
  e.preventDefault();
2654
2688
  let n = t[this._activeCell.col];
2655
2689
  n && this._isCellEditable(n) ? this._startEdit() : e.key === "Enter" && this._fireRowActivate(n);
@@ -2737,7 +2771,7 @@ var q = {
2737
2771
  async _handleCopy(e) {
2738
2772
  let t = this._selection.getEffectiveRange();
2739
2773
  if (!t) return;
2740
- let n = this.visibleColumns, r = oe(this._sortedIndices.map((e) => this.data[e]), n, t);
2774
+ let n = this.visibleColumns, r = se(this._sortedIndices.map((e) => this.data[e]), n, t);
2741
2775
  try {
2742
2776
  await navigator.clipboard.writeText(r);
2743
2777
  } catch (e) {
@@ -2763,7 +2797,7 @@ var q = {
2763
2797
  if (!this._activeCell) return;
2764
2798
  let e = await this._readClipboardText();
2765
2799
  if (e == null) return;
2766
- let t = h(e);
2800
+ let t = g(e);
2767
2801
  if (t.length === 0) return;
2768
2802
  let n = this._expandRowsForPaste(this._activeCell.row, t.length), r = this._applyPasteData(this._activeCell, t);
2769
2803
  if (r.length > 0 || n.length > 0) {
@@ -3492,7 +3526,7 @@ var q = {
3492
3526
  i[t] = {
3493
3527
  ...i[t],
3494
3528
  text: r,
3495
- value: r.trim() === "" ? null : m(r)
3529
+ value: r.trim() === "" ? null : h(r)
3496
3530
  }, this._numberFilterState.set(e, i), this._emptyFilterState.delete(e), this._applyFilterForKey(e);
3497
3531
  }}
3498
3532
  @keydown=${(e) => {
@@ -3998,7 +4032,7 @@ var q = {
3998
4032
  this._closeFindPanel();
3999
4033
  return;
4000
4034
  }
4001
- e.key === "Enter" && !_(e) && e.target instanceof HTMLInputElement && e.target.classList.contains("ft-find-input") && (e.shiftKey ? this._findPrev() : this._findNext());
4035
+ e.key === "Enter" && !v(e) && e.target instanceof HTMLInputElement && e.target.classList.contains("ft-find-input") && (e.shiftKey ? this._findPrev() : this._findNext());
4002
4036
  }}>
4003
4037
  <div class="ft-find-row">
4004
4038
  <input class="ft-find-input" type="text" placeholder=${A("findPlaceholder")}
@@ -4209,7 +4243,7 @@ var q = {
4209
4243
  ${this._renderEditor(e, t)}
4210
4244
  </div>
4211
4245
  `;
4212
- let v = s || l, y = this._isCellInvalid(n, r), b = (this._findState?.results ?? []).findIndex((e) => e.row === n && e.col === r), x = b >= 0, S = x && b === this._findState.currentIndex, w = [
4246
+ let v = s || l, y = this._isCellInvalid(n, r), b = (this._findState?.results ?? []).findIndex((e) => e.row === n && e.col === r), x = b >= 0, S = x && b === this._findState.currentIndex, C = [
4213
4247
  "ft-cell",
4214
4248
  `ft-type-${t.type ?? "text"}`,
4215
4249
  `ft-align-${G(t)}`,
@@ -4220,10 +4254,11 @@ var q = {
4220
4254
  S ? "ft-find-current" : x ? "ft-find-match" : "",
4221
4255
  o ? `ft-merge-${o.headParity}` : "",
4222
4256
  o?.continues ? "ft-merge-continued" : "",
4223
- o?.opensDown ? "ft-merge-open" : ""
4257
+ o?.opensDown ? "ft-merge-open" : "",
4258
+ t.reveal === "hover" ? "ft-reveal-hover" : ""
4224
4259
  ].filter(Boolean).join(" "), T = this._toDataIndex(n), E = this._comments.get(T)?.get(t.key) ?? null, D = E !== null, O = D ? i`<div class="ft-comment-indicator" title=${E}></div>` : a;
4225
4260
  return i`
4226
- <div class=${D ? w + " ft-has-comment" : w}
4261
+ <div class=${D ? C + " ft-has-comment" : C}
4227
4262
  role="gridcell"
4228
4263
  aria-colindex=${r + 1}
4229
4264
  data-col-index=${r}
@@ -4237,7 +4272,7 @@ var q = {
4237
4272
  @click=${(e) => this._onCellClickEvent(e, n, r)}
4238
4273
  @dblclick=${() => this._onCellDblClick(n, r)}>
4239
4274
  ${O}
4240
- ${C(e[t.key], e, t)}
4275
+ ${w(e[t.key], e, t)}
4241
4276
  </div>
4242
4277
  `;
4243
4278
  }
@@ -4252,19 +4287,16 @@ var q = {
4252
4287
  `;
4253
4288
  if (t.type === "date") return i`
4254
4289
  <input class="ft-editor ft-editor-date" type="text" inputmode="numeric" placeholder=${u()}
4255
- .value=${se(n)}
4290
+ .value=${ce(n)}
4256
4291
  @keydown=${this._onEditorKeyDown}
4257
4292
  @blur=${() => this._commitEdit()}>
4258
4293
  `;
4259
- if (t.type === "datetime") {
4260
- let e = this._toDateTimeInputValue(n);
4261
- return i`
4262
- <input class="ft-editor" type="datetime-local"
4263
- .value=${e}
4294
+ if (t.type === "datetime") return i`
4295
+ <input class="ft-editor ft-editor-datetime" type="text" placeholder=${`${u()} HH:mm`}
4296
+ .value=${le(n)}
4264
4297
  @keydown=${this._onEditorKeyDown}
4265
4298
  @blur=${() => this._commitEdit()}>
4266
4299
  `;
4267
- }
4268
4300
  if (t.type === "select" && t.options && t.options.length > 0) {
4269
4301
  let e = t.options, r = typeof e[0] == "string";
4270
4302
  return i`
@@ -4305,11 +4337,6 @@ var q = {
4305
4337
  @blur=${() => this._commitEdit()}>
4306
4338
  `;
4307
4339
  }
4308
- _toDateTimeInputValue(e) {
4309
- if (!e) return "";
4310
- let t = e instanceof Date ? e : new Date(String(e));
4311
- 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")}`;
4312
- }
4313
4340
  };
4314
4341
  K([s({ type: Array })], $.prototype, "columns", void 0), K([s({
4315
4342
  type: Array,
@@ -4367,4 +4394,4 @@ K([s({ type: Array })], $.prototype, "columns", void 0), K([s({
4367
4394
  attribute: "max-undo-size"
4368
4395
  })], $.prototype, "maxUndoSize", null), K([c()], $.prototype, "_scrollTop", void 0), K([c()], $.prototype, "_scrollLeft", void 0), K([c()], $.prototype, "_viewportHeight", void 0), K([c()], $.prototype, "_viewportWidth", void 0), K([c()], $.prototype, "_activeCell", void 0), K([c()], $.prototype, "_editingCell", void 0), K([c()], $.prototype, "_sortCriteria", void 0), K([c()], $.prototype, "_openFilterKey", void 0), K([c()], $.prototype, "_rowSelectionVersion", void 0), K([c()], $.prototype, "_autocompleteState", void 0), K([c()], $.prototype, "_headerMenu", void 0), K([c()], $.prototype, "_bodyContextMenu", void 0), K([c()], $.prototype, "_commentPopup", void 0), K([c()], $.prototype, "_isDragOver", void 0), $ = K([o("flex-table")], $);
4369
4396
  //#endregion
4370
- export { j as a, M as i, G as n, k as o, B as r, C as s, $ as t };
4397
+ export { j as a, M as i, G as n, k as o, B as r, w as s, $ as t };
@@ -525,9 +525,6 @@ export declare class FlexTable extends LitElement {
525
525
  private _mergeState;
526
526
  private _renderCell;
527
527
  private _renderEditor;
528
- /** Convert value to YYYY-MM-DD for date input (local timezone) */
529
- /** Convert value to YYYY-MM-DDTHH:mm for datetime-local input (local timezone) */
530
- private _toDateTimeInputValue;
531
528
  }
532
529
  declare global {
533
530
  interface HTMLElementTagNameMap {
@@ -1,2 +1,2 @@
1
- import { a as e, i as t, n, o as r, r as i, s as a, t as o } from "./flex-table-C6PFutM_.js";
1
+ import { a as e, i as t, n, o as r, r as i, s as a, t as o } from "./flex-table-BdnkC234.js";
2
2
  export { o as FlexTable, e as RowSelectionState, t as UndoStack, n as effectiveAlign, i as exportData, r as flexTableLocale, a as renderCell };
package/dist/locale.d.ts CHANGED
@@ -25,7 +25,7 @@
25
25
  * (아이콘 자리의 글리프)는 어느 로케일에서도 같은 것을 뜻하며, 번역하면 오히려 읽히지 않는다.
26
26
  * 그 판단은 «번역하지 않기로 한 것»이지 «빠뜨린 것»이 아니다.
27
27
  */
28
- export type FlexTableMessageKey = 'showHiddenColumns' | 'columnMenu' | 'cellActions' | 'noColumnsDefined' | 'addCommentPlaceholder' | 'cancel' | 'save' | 'clear' | 'blankCells' | 'emptyOnly' | 'nonEmptyOnly' | 'blankAll' | 'all' | 'contains' | 'startsWith' | 'endsWith' | 'wildcard' | 'searchPlaceholder' | 'valuePlaceholder' | 'fromPlaceholder' | 'toPlaceholder' | 'findPlaceholder' | 'findPrevious' | 'findNext' | 'matchCase' | 'wholeCell' | 'closeFind' | 'replaceWithPlaceholder' | 'replace' | 'replaceAll' | 'sortAscending' | 'sortDescending' | 'filter' | 'clearFilter' | 'hideColumn' | 'showColumn' | 'autoFitWidth' | 'wider' | 'narrower' | 'columnMenuFor' | 'columnMenuRegion' | 'copy' | 'insertRowAbove' | 'insertRowBelow' | 'deleteRow' | 'filterByThisValue' | 'addComment' | 'editComment' | 'deleteComment' | 'booleanTrue' | 'booleanFalse' | 'dropFileToImport' | 'notANumber' | 'notADate' | 'notInList';
28
+ export type FlexTableMessageKey = 'showHiddenColumns' | 'columnMenu' | 'cellActions' | 'noColumnsDefined' | 'addCommentPlaceholder' | 'cancel' | 'save' | 'clear' | 'blankCells' | 'emptyOnly' | 'nonEmptyOnly' | 'blankAll' | 'all' | 'contains' | 'startsWith' | 'endsWith' | 'wildcard' | 'searchPlaceholder' | 'valuePlaceholder' | 'fromPlaceholder' | 'toPlaceholder' | 'findPlaceholder' | 'findPrevious' | 'findNext' | 'matchCase' | 'wholeCell' | 'closeFind' | 'replaceWithPlaceholder' | 'replace' | 'replaceAll' | 'sortAscending' | 'sortDescending' | 'filter' | 'clearFilter' | 'hideColumn' | 'showColumn' | 'autoFitWidth' | 'wider' | 'narrower' | 'columnMenuFor' | 'columnMenuRegion' | 'copy' | 'insertRowAbove' | 'insertRowBelow' | 'deleteRow' | 'filterByThisValue' | 'addComment' | 'editComment' | 'deleteComment' | 'booleanTrue' | 'booleanFalse' | 'dropFileToImport' | 'notANumber' | 'notADate' | 'notADateTime' | 'notInList';
29
29
  /** 이 패키지의 chrome 문자열 묶음. 소비자가 `register()` 로 언어를 더하거나 문구를 덮을 수 있다. */
30
30
  export declare const flexTableLocale: import("@iyulab/components/dist/utilities/Locale.js").LocaleNamespace<FlexTableMessageKey>;
31
31
  /** 짧은 조회 별칭 — 렌더 코드가 읽히게 유지한다. */
@@ -115,6 +115,15 @@ export interface ColumnDefinition<T = DataRow> {
115
115
  * the top of the scrolled view and below frozen rows, so it never scrolls out of sight.
116
116
  */
117
117
  mergeRepeated?: boolean | ((row: T, previousRow: T, col: ColumnDefinition<T>) => boolean);
118
+ /**
119
+ * When the column's content shows.
120
+ * - `'always'` (default)
121
+ * - `'hover'`: only while the row is hovered, holds focus, or is selected — for a row-actions
122
+ * column (edit, a "more" menu), so a list does not repeat the same buttons on every row. The
123
+ * content stays in the DOM and keyboard focus reveals it; on touch screens (no hover) it is
124
+ * always shown, and it is not printed.
125
+ */
126
+ reveal?: 'always' | 'hover';
118
127
  }
119
128
  /** Style applied to a cell by a conditional formatting rule */
120
129
  export interface CellStyle {
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as e } from "./flex-table-C6PFutM_.js";
1
+ import { t as e } from "./flex-table-BdnkC234.js";
2
2
  import { i as t, t as n } from "./query-Dw4iO0ir.js";
3
3
  import { t as r } from "./view-DiLr-TdY.js";
4
4
  import i, { useCallback as a, useEffect as o, useMemo as s, useRef as c, useState as l } from "react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iyulab/flex-table",
3
- "version": "0.41.1",
3
+ "version": "0.42.0",
4
4
  "description": "A minimalist, input-centric data grid web component",
5
5
  "type": "module",
6
6
  "main": "./dist/flex-table.js",
@@ -72,7 +72,7 @@
72
72
  "odata-query": "^8.1.0"
73
73
  },
74
74
  "peerDependencies": {
75
- "@iyulab/components": ">=1.55.0",
75
+ "@iyulab/components": ">=1.56.0",
76
76
  "@lit/react": "^1.0.8",
77
77
  "react": "^18.0.0 || ^19.0.0"
78
78
  },
@@ -4,7 +4,6 @@ description: Spreadsheet-grade data grid web component (`<flex-table>`, built wi
4
4
  license: MIT
5
5
  metadata:
6
6
  author: iyulab
7
- version: "0.40.1"
8
7
  ---
9
8
 
10
9
  # @iyulab/flex-table
@@ -67,7 +66,7 @@ as `text`). See `references/api.md` for every field.
67
66
  - `editor` returns a Lit template containing an element with class `ft-editor`; its `.value` is committed.
68
67
  - `validator` returns `null` when valid, or an error message (the edit is rejected and `validation-error` fires).
69
68
  - `number` cells, the number filter and paste read numbers in the active `Locale` (`1,5` is 1.5 on a comma-decimal page); an edit that is not a number is rejected with `validation-error`.
70
- - `date` cells edit as a `YYYY-MM-DD` text box in every browser language (also `20261002`, `2026/10/2`, `10-02`); pasted dates are normalized to ISO.
69
+ - `date` cells edit as a `YYYY-MM-DD` text box in every browser language (also `20261002`, `2026/10/2`, `10-02`); pasted dates are normalized to ISO. `datetime` cells edit as `YYYY-MM-DD HH:mm` (local) and store `YYYY-MM-DDTHH:mm`.
71
70
  - `conditionalRules` applies `{ when, style }` rules in order; later matches override earlier ones.
72
71
  - `pinned: 'left' | 'right'` freezes a column during horizontal scroll.
73
72
 
@@ -66,6 +66,7 @@ Replacing `data` with the same array reference does not trigger the clear-on-cha
66
66
  | `validator` | `CellValidator<T>` | `(value, row, col) => string \| null \| undefined` |
67
67
  | `conditionalRules` | `ConditionalRule<T>[]` | `{ when(value, row, col): boolean, style: CellStyle }` |
68
68
  | `mergeRepeated` | `boolean \| (row, previousRow, col) => boolean` | Draw a run of repeated values as one merged cell; values, sort, filter and export are unchanged |
69
+ | `reveal` | `'always' \| 'hover'` | `'hover'`: content shows only while the row is hovered, focused or selected — for a row-actions column; stays focusable, always shown without hover, not printed |
69
70
 
70
71
  `CellStyle` = `{ background?, color?, fontWeight?: 'bold' | 'normal', fontStyle?: 'italic' | 'normal' }`.
71
72