@iyulab/flex-table 0.21.1 → 0.23.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/README.md CHANGED
@@ -67,7 +67,7 @@ npm install @iyulab/flex-table
67
67
  |----------|-----------|------|---------|-------------|
68
68
  | `columns` | — | `ColumnDefinition[]` | `[]` | Column definitions |
69
69
  | `data` | — | `DataRow[]` | `[]` | Data rows (`Record<string, unknown>[]`) |
70
- | `rowHeight` | `row-height` | `number` | `32` | Row height in pixels |
70
+ | `rowHeight` | `row-height` | `number` | `32` | Row height in pixels. Falls back to the `--ft-row-height` token when not set — see [Density](#density-and-header-hierarchy) |
71
71
  | `showRowNumbers` | `show-row-numbers` | `boolean` | `false` | Show row number column |
72
72
  | `theme` | `theme` | `'light' \| 'dark'` | auto | Force theme; auto-detects `prefers-color-scheme` |
73
73
  | `editable` | `editable` | `boolean` | `true` | Global read-only mode when `false` |
@@ -269,30 +269,83 @@ All events use `CustomEvent` with `bubbles: true, composed: true`.
269
269
 
270
270
  ## CSS Custom Properties
271
271
 
272
- All colors and styles are customizable via CSS custom properties:
272
+ All colors and styles are customizable via CSS custom properties.
273
+
274
+ **Two levels, pick whichever fits.** Since 0.22.0 every `--ft-*` colour is derived from
275
+ the `@iyulab/components` design tokens, so if you already load that token sheet the table
276
+ follows your brand with no per-table configuration:
277
+
278
+ ```css
279
+ :root { --u-primary-color: #7b1fa2; } /* the table's accent, selection and boolean
280
+ markers follow — so do the buttons and shell */
281
+ ```
282
+
283
+ Override an individual `--ft-*` when you want the table to differ from the rest of the app:
273
284
 
274
285
  ```css
275
286
  flex-table {
276
287
  --ft-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
277
288
  --ft-font-size: 14px;
278
- --ft-border-color: #e0e0e0;
279
- --ft-bg: #fff;
280
- --ft-text-color: #202124;
281
- --ft-header-bg: #f8f9fa;
282
- --ft-header-hover-bg: #e8eaed;
283
- --ft-header-text-color: #202124;
284
- --ft-row-even-bg: #fff;
285
- --ft-row-odd-bg: #fafafa;
286
- --ft-row-hover-bg: #f0f4ff;
287
- --ft-active-color: #1a73e8;
288
- --ft-selection-bg: #e8f0fe;
289
- --ft-bool-color: #2196f3;
290
- --ft-sort-indicator-color: #5f6368;
291
- --ft-editor-bg: #fff;
292
- --ft-empty-color: #999;
289
+
290
+ /* Surfaces and text derived from */
291
+ --ft-bg: #fff; /* --u-bg-color */
292
+ --ft-text-color: #202124; /* --u-txt-color */
293
+ --ft-border-color: #e0e0e0; /* --u-border-color */
294
+ --ft-header-bg: #f8f9fa; /* --u-bg-color-raised */
295
+ --ft-header-hover-bg: #e8eaed; /* --u-bg-color-active */
296
+ --ft-header-text-color: #202124; /* --u-txt-color */
297
+ --ft-row-even-bg: #fff; /* --u-bg-color */
298
+ --ft-row-odd-bg: #fafafa; /* --u-bg-color-raised */
299
+ --ft-row-hover-bg: #f0f4ff; /* --u-bg-color-hover */
300
+ --ft-editor-bg: #fff; /* --u-input-bg-color */
301
+ --ft-sort-indicator-color: #5f6368;/* --u-txt-color-weak */
302
+ --ft-empty-color: #5f6368; /* --u-txt-color-weak */
303
+
304
+ /* Accent */
305
+ --ft-active-color: #1a73e8; /* --u-primary-color */
306
+ --ft-selection-bg: #e8f0fe; /* --u-primary-bg-color */
307
+ --ft-bool-color: #2196f3; /* --u-primary-color */
308
+
309
+ /* State overlays — these are translucent on purpose, so the row striping and
310
+ selection underneath stay visible. Set the *-color and the background follows. */
311
+ --ft-invalid-color: #d93025; /* --u-danger-color */
312
+ --ft-drop-color: #1a73e8; /* --u-primary-color */
313
+ --ft-find-color: #f9a825; /* --u-warning-color */
314
+ }
315
+ ```
316
+
317
+ ### Density and header hierarchy
318
+
319
+ Since 0.23.0 the vertical rhythm and the header's typographic weight are adjustable, so a
320
+ table can be matched to the other tables on the page:
321
+
322
+ ```css
323
+ flex-table {
324
+ --ft-row-height: 32px; /* body row height */
325
+ --ft-cell-padding-block: 6px; /* text placement inside that height */
326
+ --ft-cell-padding-inline: 12px;
327
+ --ft-header-font-size: 14px; /* defaults to --ft-font-size */
328
+ --ft-header-font-weight: 600;
293
329
  }
294
330
  ```
295
331
 
332
+ ⚠ **`--ft-row-height` is read once, at first render.** The grid is virtualised — rows are
333
+ positioned at `index × rowHeight`, so the height cannot come from the cascade the way
334
+ padding does. Declare it in a stylesheet that is in effect before the table is attached; to
335
+ change it later, set the `rowHeight` property (or the `row-height` attribute) instead. An
336
+ explicitly set property or attribute always wins over the token, and a value in any unit
337
+ other than `px` is ignored.
338
+
339
+ ⚠ **Row height is fixed, and padding places the text within it.** Cells are single-line
340
+ (`nowrap` + ellipsis) by design. If you reduce `--ft-row-height`, reduce
341
+ `--ft-cell-padding-block` to match — keep `padding-block × 2 + line box ≤ row-height`, or
342
+ the text is clipped at the bottom.
343
+
344
+ **Without the token sheet nothing changes.** Every reference carries the literal above as
345
+ its fallback, and the built-in dark theme (`prefers-color-scheme` or `theme="dark"`) still
346
+ applies — the table remains usable standalone. Referencing the tokens does not add a
347
+ package dependency; CSS custom properties are resolved at render time, not imported.
348
+
296
349
  ## Keyboard Shortcuts
297
350
 
298
351
  | Key | Action |
@@ -6,64 +6,98 @@ var s = t`
6
6
  :host {
7
7
  --ft-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
8
8
  --ft-font-size: 14px;
9
- --ft-border-color: #e0e0e0;
10
- --ft-bg: #fff;
11
- --ft-text-color: #202124;
12
- --ft-header-bg: #f8f9fa;
13
- --ft-header-hover-bg: #e8eaed;
14
- --ft-header-text-color: #202124;
15
- --ft-row-even-bg: #fff;
16
- --ft-row-odd-bg: #fafafa;
17
- --ft-row-hover-bg: #f0f4ff;
18
- --ft-active-color: #1a73e8;
19
- --ft-selection-bg: #e8f0fe;
20
- --ft-bool-color: #2196f3;
21
- --ft-sort-indicator-color: #5f6368;
22
- --ft-editor-bg: #fff;
9
+
10
+ /* --- 치수·위계 축 (기본값 = 종전 렌더값) ---
11
+ 표는 가상 스크롤이라 행 높이가 **JS 로 계산된다**(index * rowHeight).
12
+ 그래서 --ft-row-height 는 CSS 만으로는 닿지 않고, 컴포넌트가 첫 렌더에서
13
+ 판독해 가상화 계산에 넣는다. 그 결과가 rowHeight 프로퍼티의 기본값이다.
14
+
15
+ ⚠여백과 행 높이의 관계: 행 높이는 **고정**이고 여백은 그 안에서 글자를
16
+ 배치한다. 행 높이를 줄이면서 여백을 그대로 두면 아래가 잘린다
17
+ (padding-block * 2 + 한 줄 높이 <= row-height 를 유지할 것). */
18
+ --ft-row-height: 32px;
19
+ --ft-cell-padding-block: 6px;
20
+ --ft-cell-padding-inline: 12px;
21
+ --ft-header-font-size: var(--ft-font-size);
22
+ --ft-header-font-weight: 600;
23
+ --ft-border-color: var(--u-border-color, #e0e0e0);
24
+ --ft-bg: var(--u-bg-color, #fff);
25
+ --ft-text-color: var(--u-txt-color, #202124);
26
+ --ft-header-bg: var(--u-bg-color-raised, #f8f9fa);
27
+ --ft-header-hover-bg: var(--u-bg-color-active, #e8eaed);
28
+ --ft-header-text-color: var(--u-txt-color, #202124);
29
+ --ft-row-even-bg: var(--u-bg-color, #fff);
30
+ --ft-row-odd-bg: var(--u-bg-color-raised, #fafafa);
31
+ --ft-row-hover-bg: var(--u-bg-color-hover, #f0f4ff);
32
+ --ft-active-color: var(--u-primary-color, #1a73e8);
33
+ --ft-selection-bg: var(--u-primary-bg-color, #e8f0fe);
34
+ --ft-bool-color: var(--u-primary-color, #2196f3);
35
+ --ft-sort-indicator-color: var(--u-txt-color-weak, #5f6368);
36
+ --ft-editor-bg: var(--u-input-bg-color, #fff);
23
37
  /* #999 was 2.85 against --ft-bg (#fff); AA needs 4.5. The dark value (#9aa0a6 on
24
38
  #1e1e2e = 6.31) was already fine, so this was a light-only defect. #5f6368 is the
25
39
  same grey the sort indicator uses, which keeps the palette to one family. */
26
- --ft-empty-color: #5f6368;
27
- }
28
-
29
- /* --- Dark Theme (auto via prefers-color-scheme) --- */
40
+ --ft-empty-color: var(--u-txt-color-weak, #5f6368);
41
+
42
+ /* 상태 색 — 종전에는 규칙 안에 리터럴로 박혀 있어 소비자가 덮을 경로가 없었고
43
+ 테마도 따라오지 않았다(다크에서 라이트용 그대로).
44
+
45
+ ★**셀 배경은 불투명 면이 아니라 반투명 겹침이다.** 줄무늬(홀/짝 행)와 선택 표시가
46
+ 아래에 있고 그것이 비쳐야 "어느 행의 어떤 셀"인지 읽힌다. 그래서 면 토큰
47
+ (--u-*-bg-color)이 아니라 **상태 색에서 알파를 뽑는다** — 부수 효과로 두 테마
48
+ 모두에서 성립하므로 아래 다크 블록에 이 셋을 다시 적을 필요가 없다. */
49
+ --ft-invalid-color: var(--u-danger-color, #d93025);
50
+ --ft-invalid-bg: color-mix(in srgb, var(--ft-invalid-color) 8%, transparent);
51
+ --ft-drop-color: var(--u-primary-color, #1a73e8);
52
+ --ft-drop-bg: color-mix(in srgb, var(--ft-drop-color) 15%, transparent);
53
+ --ft-find-color: var(--u-warning-color, #f9a825);
54
+ --ft-find-bg: color-mix(in srgb, var(--ft-find-color) 25%, transparent);
55
+ --ft-find-current-bg: color-mix(in srgb, var(--ft-find-color) 50%, transparent);
56
+ }
57
+
58
+ /* --- Dark Theme (auto via prefers-color-scheme) ---
59
+ ★**토큰 시트가 로드된 환경에서 아래 두 블록은 아무 일도 하지 않는다** — --u-* 가
60
+ 이미 테마별로 다른 값을 가리키므로 base 규칙만으로 두 테마가 성립한다.
61
+ 남겨 두는 이유는 **시트가 없는 소비자**다: 그때는 각 var() 의 리터럴 폴백이
62
+ 발동하고, 아래 블록이 그 폴백을 다크 값으로 바꿔 준다.
63
+ ⇒ 즉 이 표는 시트 없이도 종전처럼 자체 테마를 갖는다(그것이 이 패키지의 계약이었다). */
30
64
  @media (prefers-color-scheme: dark) {
31
65
  :host(:not([theme="light"])) {
32
- --ft-border-color: #3c4043;
33
- --ft-bg: #1e1e1e;
34
- --ft-text-color: #e8eaed;
35
- --ft-header-bg: #292a2d;
36
- --ft-header-hover-bg: #3c4043;
37
- --ft-header-text-color: #e8eaed;
38
- --ft-row-even-bg: #1e1e1e;
39
- --ft-row-odd-bg: #252526;
40
- --ft-row-hover-bg: #2a2d2e;
41
- --ft-active-color: #4da3ff;
42
- --ft-selection-bg: #264f78;
43
- --ft-bool-color: #64b5f6;
44
- --ft-sort-indicator-color: #9aa0a6;
45
- --ft-editor-bg: #2d2d2d;
46
- --ft-empty-color: #9aa0a6;
66
+ --ft-border-color: var(--u-border-color, #3c4043);
67
+ --ft-bg: var(--u-bg-color, #1e1e1e);
68
+ --ft-text-color: var(--u-txt-color, #e8eaed);
69
+ --ft-header-bg: var(--u-bg-color-raised, #292a2d);
70
+ --ft-header-hover-bg: var(--u-bg-color-active, #3c4043);
71
+ --ft-header-text-color: var(--u-txt-color, #e8eaed);
72
+ --ft-row-even-bg: var(--u-bg-color, #1e1e1e);
73
+ --ft-row-odd-bg: var(--u-bg-color-raised, #252526);
74
+ --ft-row-hover-bg: var(--u-bg-color-hover, #2a2d2e);
75
+ --ft-active-color: var(--u-primary-color, #4da3ff);
76
+ --ft-selection-bg: var(--u-primary-bg-color, #264f78);
77
+ --ft-bool-color: var(--u-primary-color, #64b5f6);
78
+ --ft-sort-indicator-color: var(--u-txt-color-weak, #9aa0a6);
79
+ --ft-editor-bg: var(--u-input-bg-color, #2d2d2d);
80
+ --ft-empty-color: var(--u-txt-color-weak, #9aa0a6);
47
81
  }
48
82
  }
49
83
 
50
84
  /* --- Force dark via attribute --- */
51
85
  :host([theme="dark"]) {
52
- --ft-border-color: #3c4043;
53
- --ft-bg: #1e1e1e;
54
- --ft-text-color: #e8eaed;
55
- --ft-header-bg: #292a2d;
56
- --ft-header-hover-bg: #3c4043;
57
- --ft-header-text-color: #e8eaed;
58
- --ft-row-even-bg: #1e1e1e;
59
- --ft-row-odd-bg: #252526;
60
- --ft-row-hover-bg: #2a2d2e;
61
- --ft-active-color: #4da3ff;
62
- --ft-selection-bg: #264f78;
63
- --ft-bool-color: #64b5f6;
64
- --ft-sort-indicator-color: #9aa0a6;
65
- --ft-editor-bg: #2d2d2d;
66
- --ft-empty-color: #9aa0a6;
86
+ --ft-border-color: var(--u-border-color, #3c4043);
87
+ --ft-bg: var(--u-bg-color, #1e1e1e);
88
+ --ft-text-color: var(--u-txt-color, #e8eaed);
89
+ --ft-header-bg: var(--u-bg-color-raised, #292a2d);
90
+ --ft-header-hover-bg: var(--u-bg-color-active, #3c4043);
91
+ --ft-header-text-color: var(--u-txt-color, #e8eaed);
92
+ --ft-row-even-bg: var(--u-bg-color, #1e1e1e);
93
+ --ft-row-odd-bg: var(--u-bg-color-raised, #252526);
94
+ --ft-row-hover-bg: var(--u-bg-color-hover, #2a2d2e);
95
+ --ft-active-color: var(--u-primary-color, #4da3ff);
96
+ --ft-selection-bg: var(--u-primary-bg-color, #264f78);
97
+ --ft-bool-color: var(--u-primary-color, #64b5f6);
98
+ --ft-sort-indicator-color: var(--u-txt-color-weak, #9aa0a6);
99
+ --ft-editor-bg: var(--u-input-bg-color, #2d2d2d);
100
+ --ft-empty-color: var(--u-txt-color-weak, #9aa0a6);
67
101
  }
68
102
 
69
103
  /* --- Layout --- */
@@ -89,10 +123,12 @@ var s = t`
89
123
  .ft-header-cell {
90
124
  position: absolute;
91
125
  top: 0;
92
- padding: 8px 12px;
126
+ /* 머리행은 종전에도 본문보다 상하 2px 넉넉했다 — 그 관계를 유지한다. */
127
+ padding: calc(var(--ft-cell-padding-block) + 2px) var(--ft-cell-padding-inline);
93
128
  background: var(--ft-header-bg);
94
129
  color: var(--ft-header-text-color);
95
- font-weight: 600;
130
+ font-size: var(--ft-header-font-size);
131
+ font-weight: var(--ft-header-font-weight);
96
132
  user-select: none;
97
133
  border-bottom: 2px solid var(--ft-border-color);
98
134
  overflow: hidden;
@@ -194,7 +230,7 @@ var s = t`
194
230
  .ft-cell {
195
231
  position: absolute;
196
232
  top: 0;
197
- padding: 6px 12px;
233
+ padding: var(--ft-cell-padding-block) var(--ft-cell-padding-inline);
198
234
  border-bottom: 1px solid var(--ft-border-color);
199
235
  overflow: hidden;
200
236
  text-overflow: ellipsis;
@@ -218,9 +254,9 @@ var s = t`
218
254
  }
219
255
 
220
256
  .ft-cell.ft-invalid {
221
- outline: 2px solid #d93025;
257
+ outline: 2px solid var(--ft-invalid-color);
222
258
  outline-offset: -2px;
223
- background: rgba(217, 48, 37, 0.08);
259
+ background: var(--ft-invalid-bg);
224
260
  }
225
261
 
226
262
  .ft-cell.ft-editing { padding: 0; overflow: visible; }
@@ -228,7 +264,8 @@ var s = t`
228
264
  .ft-editor {
229
265
  width: 100%; height: 100%;
230
266
  border: none; outline: none;
231
- padding: 6px 12px;
267
+ /* 편집기는 셀 위에 겹쳐 뜬다 — 여백이 어긋나면 글자가 튄다. */
268
+ padding: var(--ft-cell-padding-block) var(--ft-cell-padding-inline);
232
269
  font: inherit;
233
270
  color: var(--ft-text-color);
234
271
  background: var(--ft-editor-bg);
@@ -312,14 +349,14 @@ var s = t`
312
349
  position: absolute;
313
350
  inset: 0;
314
351
  z-index: 100;
315
- background: rgba(59, 130, 246, 0.15);
316
- border: 2px dashed rgba(59, 130, 246, 0.8);
352
+ background: var(--ft-drop-bg);
353
+ border: 2px dashed var(--ft-drop-color);
317
354
  display: flex;
318
355
  align-items: center;
319
356
  justify-content: center;
320
357
  font-size: 14px;
321
358
  font-weight: 500;
322
- color: rgb(37, 99, 235);
359
+ color: var(--ft-drop-color);
323
360
  pointer-events: none;
324
361
  border-radius: 4px;
325
362
  }
@@ -692,7 +729,7 @@ var s = t`
692
729
 
693
730
  .ft-comment-popup-textarea:focus {
694
731
  border-color: var(--ft-active-color, #3b82f6);
695
- box-shadow: 0 0 0 2px rgba(59,130,246,0.2);
732
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--ft-active-color) 20%, transparent);
696
733
  }
697
734
 
698
735
  .ft-comment-popup-buttons {
@@ -801,12 +838,12 @@ var s = t`
801
838
 
802
839
  /* Find highlight */
803
840
  .ft-cell.ft-find-match {
804
- background: rgba(255, 200, 0, 0.25) !important;
841
+ background: var(--ft-find-bg) !important;
805
842
  }
806
843
 
807
844
  .ft-cell.ft-find-current {
808
- background: rgba(255, 160, 0, 0.5) !important;
809
- outline: 2px solid orange;
845
+ background: var(--ft-find-current-bg) !important;
846
+ outline: 2px solid var(--ft-find-color);
810
847
  outline-offset: -2px;
811
848
  }
812
849
  `;
@@ -1290,7 +1327,7 @@ function oe(e, t) {
1290
1327
  <sheetData>${n.join("")}</sheetData>
1291
1328
  </worksheet>`;
1292
1329
  }
1293
- function z(e, t) {
1330
+ function se(e, t) {
1294
1331
  let n = oe(e, t);
1295
1332
  return ne([
1296
1333
  {
@@ -1321,24 +1358,24 @@ function z(e, t) {
1321
1358
  }
1322
1359
  //#endregion
1323
1360
  //#region src/export/export.ts
1324
- function B(e, t, n) {
1361
+ function z(e, t, n) {
1325
1362
  switch (n) {
1326
- case "csv": return H(e, t, ",");
1327
- case "tsv": return H(e, t, " ");
1328
- case "json": return W(e, t);
1329
- case "xlsx": return z(e, t);
1363
+ case "csv": return V(e, t, ",");
1364
+ case "tsv": return V(e, t, " ");
1365
+ case "json": return U(e, t);
1366
+ case "xlsx": return se(e, t);
1330
1367
  }
1331
1368
  }
1332
- function V(e, t) {
1369
+ function B(e, t) {
1333
1370
  return e == null ? "" : (t.type === "date" || t.type === "datetime") && e instanceof Date ? e.toISOString() : String(e);
1334
1371
  }
1335
- function H(e, t, n) {
1336
- return [t.map((e) => U(e.header, n)).join(n), ...e.map((e) => t.map((t) => U(V(e[t.key], t), n)).join(n))].join("\n");
1372
+ function V(e, t, n) {
1373
+ return [t.map((e) => H(e.header, n)).join(n), ...e.map((e) => t.map((t) => H(B(e[t.key], t), n)).join(n))].join("\n");
1337
1374
  }
1338
- function U(e, t) {
1375
+ function H(e, t) {
1339
1376
  return e.includes(t) || e.includes("\"") || e.includes("\n") || e.includes("\r") ? "\"" + e.replace(/"/g, "\"\"") + "\"" : e;
1340
1377
  }
1341
- function W(e, t) {
1378
+ function U(e, t) {
1342
1379
  let n = e.map((e) => {
1343
1380
  let n = {};
1344
1381
  for (let r of t) {
@@ -1349,35 +1386,35 @@ function W(e, t) {
1349
1386
  });
1350
1387
  return JSON.stringify(n, null, 2);
1351
1388
  }
1352
- function G(e, t, n) {
1389
+ function W(e, t, n) {
1353
1390
  let r = new Blob([e], { type: n }), i = URL.createObjectURL(r), a = document.createElement("a");
1354
1391
  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);
1355
1392
  }
1356
- var K = {
1393
+ var G = {
1357
1394
  csv: "text/csv;charset=utf-8",
1358
1395
  tsv: "text/tab-separated-values;charset=utf-8",
1359
1396
  json: "application/json;charset=utf-8",
1360
1397
  xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
1361
- }, se = {
1398
+ }, ce = {
1362
1399
  csv: ".csv",
1363
1400
  tsv: ".tsv",
1364
1401
  json: ".json",
1365
1402
  xlsx: ".xlsx"
1366
1403
  };
1367
- function ce(e) {
1368
- return K[e];
1369
- }
1370
1404
  function le(e) {
1371
- return se[e];
1405
+ return G[e];
1406
+ }
1407
+ function ue(e) {
1408
+ return ce[e];
1372
1409
  }
1373
1410
  //#endregion
1374
1411
  //#region src/export/xlsx-reader.ts
1375
- function ue(e) {
1412
+ function de(e) {
1376
1413
  for (let t = e.byteLength - 22; t >= 0; t--) if (e.getUint32(t, !0) === 101010256) return t;
1377
1414
  throw Error("XLSX: End of central directory not found — not a valid ZIP file");
1378
1415
  }
1379
- function de(e) {
1380
- let t = ue(e), n = e.getUint32(t + 16, !0), r = e.getUint32(t + 12, !0), i = new TextDecoder("utf-8"), a = [], o = n, s = n + r;
1416
+ function fe(e) {
1417
+ 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;
1381
1418
  for (; o < s && e.getUint32(o, !0) === 33639248;) {
1382
1419
  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));
1383
1420
  a.push({
@@ -1390,7 +1427,7 @@ function de(e) {
1390
1427
  }
1391
1428
  return a;
1392
1429
  }
1393
- async function fe(e) {
1430
+ async function pe(e) {
1394
1431
  let t = new DecompressionStream("deflate-raw"), n = t.writable.getWriter(), r = t.readable.getReader();
1395
1432
  n.write(e), n.close();
1396
1433
  let i = [];
@@ -1403,40 +1440,40 @@ async function fe(e) {
1403
1440
  for (let e of i) o.set(e, s), s += e.length;
1404
1441
  return o;
1405
1442
  }
1406
- async function pe(e, t) {
1443
+ async function me(e, t) {
1407
1444
  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);
1408
1445
  if (t.compressionMethod === 0) return o.slice();
1409
- if (t.compressionMethod === 8) return fe(o);
1446
+ if (t.compressionMethod === 8) return pe(o);
1410
1447
  throw Error(`XLSX: Unsupported compression method ${t.compressionMethod}`);
1411
1448
  }
1412
- async function me(e) {
1413
- let t = new DataView(e), n = de(t), r = /* @__PURE__ */ new Map();
1414
- for (let e of n) e.compressedSize === 0 && e.name.endsWith("/") || r.set(e.name, await pe(t, e));
1449
+ async function he(e) {
1450
+ let t = new DataView(e), n = fe(t), r = /* @__PURE__ */ new Map();
1451
+ for (let e of n) e.compressedSize === 0 && e.name.endsWith("/") || r.set(e.name, await me(t, e));
1415
1452
  return r;
1416
1453
  }
1417
- var he = new TextDecoder("utf-8");
1418
- function q(e) {
1419
- let t = he.decode(e);
1454
+ var ge = new TextDecoder("utf-8");
1455
+ function K(e) {
1456
+ let t = ge.decode(e);
1420
1457
  return new DOMParser().parseFromString(t, "application/xml");
1421
1458
  }
1422
- function ge(e) {
1459
+ function _e(e) {
1423
1460
  if (!e) return [];
1424
- let t = q(e);
1461
+ let t = K(e);
1425
1462
  return Array.from(t.querySelectorAll("si")).map((e) => Array.from(e.querySelectorAll("t")).map((e) => e.textContent ?? "").join(""));
1426
1463
  }
1427
- function _e(e) {
1464
+ function ve(e) {
1428
1465
  let t = (e - 1) * 864e5 + Date.UTC(1899, 11, 30);
1429
1466
  return new Date(t).toISOString().slice(0, 10);
1430
1467
  }
1431
- function ve(e, t) {
1432
- let n = q(e).querySelectorAll("row");
1468
+ function ye(e, t) {
1469
+ let n = K(e).querySelectorAll("row");
1433
1470
  if (n.length === 0) return [];
1434
1471
  let r = 0, i = /* @__PURE__ */ new Map();
1435
1472
  for (let e of n) {
1436
1473
  let n = parseInt(e.getAttribute("r") ?? "1", 10) - 1, a = /* @__PURE__ */ new Map();
1437
1474
  i.set(n, a);
1438
1475
  for (let n of e.querySelectorAll("c")) {
1439
- let e = ye(n.getAttribute("r") ?? "");
1476
+ let e = be(n.getAttribute("r") ?? "");
1440
1477
  e > r && (r = e);
1441
1478
  let i = n.getAttribute("t") ?? "", o = n.getAttribute("s") ?? "", s = n.querySelector("v")?.textContent ?? "", c = n.querySelector("is > t")?.textContent ?? null, l;
1442
1479
  if (i === "s") l = t[parseInt(s, 10)] ?? "";
@@ -1445,7 +1482,7 @@ function ve(e, t) {
1445
1482
  else if (s === "") l = "";
1446
1483
  else {
1447
1484
  let e = parseFloat(s);
1448
- l = !isNaN(e) && o !== "" && parseInt(o, 10) > 0 && e >= 1 && e <= 73050 ? _e(e) : s;
1485
+ l = !isNaN(e) && o !== "" && parseInt(o, 10) > 0 && e >= 1 && e <= 73050 ? ve(e) : s;
1449
1486
  }
1450
1487
  a.set(e, l);
1451
1488
  }
@@ -1459,13 +1496,13 @@ function ve(e, t) {
1459
1496
  }
1460
1497
  return o;
1461
1498
  }
1462
- function ye(e) {
1499
+ function be(e) {
1463
1500
  let t = 0, n = 0;
1464
1501
  for (; n < e.length && e[n] >= "A" && e[n] <= "Z";) t = t * 26 + (e.charCodeAt(n) - 64), n++;
1465
1502
  return t - 1;
1466
1503
  }
1467
- async function be(e) {
1468
- let t = await me(e), n = ge(t.get("xl/sharedStrings.xml")), r = t.get("xl/worksheets/sheet1.xml");
1504
+ async function xe(e) {
1505
+ let t = await he(e), n = _e(t.get("xl/sharedStrings.xml")), r = t.get("xl/worksheets/sheet1.xml");
1469
1506
  if (!r) {
1470
1507
  for (let [e, n] of t) if (e.toLowerCase().includes("worksheets/sheet")) {
1471
1508
  r = n;
@@ -1473,7 +1510,7 @@ async function be(e) {
1473
1510
  }
1474
1511
  }
1475
1512
  if (!r) throw Error("XLSX: No worksheet found");
1476
- let i = ve(r, n);
1513
+ let i = ye(r, n);
1477
1514
  return i.length === 0 ? {
1478
1515
  headers: [],
1479
1516
  rows: []
@@ -1483,8 +1520,8 @@ async function be(e) {
1483
1520
  };
1484
1521
  }
1485
1522
  //#endregion
1486
- //#region \0@oxc-project+runtime@0.139.0/helpers/esm/decorate.js
1487
- function J(e, t, n, r) {
1523
+ //#region \0@oxc-project+runtime@0.142.0/helpers/esm/decorate.js
1524
+ function q(e, t, n, r) {
1488
1525
  var i = arguments.length, a = i < 3 ? t : r === null ? r = Object.getOwnPropertyDescriptor(t, n) : r, o;
1489
1526
  if (typeof Reflect == "object" && typeof Reflect.decorate == "function") a = Reflect.decorate(e, t, n, r);
1490
1527
  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);
@@ -1492,16 +1529,16 @@ function J(e, t, n, r) {
1492
1529
  }
1493
1530
  //#endregion
1494
1531
  //#region src/flex-table.ts
1495
- var Y = {
1532
+ var J = {
1496
1533
  eq: "=",
1497
1534
  neq: "≠",
1498
1535
  gt: ">",
1499
1536
  lt: "<",
1500
1537
  gte: "≥",
1501
1538
  lte: "≤"
1502
- }, X = 120, Z = 40, xe = 32, Q = 5, $ = class extends e {
1539
+ }, Y = 120, X = 40, Z = 32, Q = 5, $ = class extends e {
1503
1540
  constructor(...e) {
1504
- super(...e), this.columns = [], this._data = [], this._inUndoRedo = !1, this.clearUndoOnDataChange = !1, this.clearSelectionOnDataChange = !1, this.rowHeight = xe, this.showRowNumbers = !1, this.theme = void 0, this.maxRows = 0, this.editable = !0, this.showFilters = !1, this.showContextMenu = !1, this.frozenRows = 0, this.loading = !1, this.importEnabled = !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._bodyContextMenu = null, this._commentPopup = 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._comments = /* @__PURE__ */ new Map(), this._isDragOver = !1, this._onCommentPopupOutsideClick = () => {
1541
+ 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.loading = !1, this.importEnabled = !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._bodyContextMenu = null, this._commentPopup = 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._comments = /* @__PURE__ */ new Map(), this._isDragOver = !1, this._onCommentPopupOutsideClick = () => {
1505
1542
  this._commitCommentPopup();
1506
1543
  }, 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();
1507
1544
  }
@@ -1515,6 +1552,13 @@ var Y = {
1515
1552
  let t = this._data, n = e !== t;
1516
1553
  this._data = e, n && !this._inUndoRedo && (this.clearUndoOnDataChange && this._undo.clear(), this.clearSelectionOnDataChange && this._rowSelection.selectedCount > 0 && (this._rowSelection.deselectAll(), this._rowSelectionVersion++, this._dispatchRowSelectionEvent())), this.requestUpdate("data", t);
1517
1554
  }
1555
+ get rowHeight() {
1556
+ return this._rowHeightExplicit ?? this._rowHeightFromCss ?? Z;
1557
+ }
1558
+ set rowHeight(e) {
1559
+ let t = this.rowHeight;
1560
+ this._rowHeightExplicit = e, this.requestUpdate("rowHeight", t);
1561
+ }
1518
1562
  set selectionMode(e) {
1519
1563
  this._rowSelection.mode = e, this.requestUpdate();
1520
1564
  }
@@ -1850,12 +1894,12 @@ var Y = {
1850
1894
  if (!t) return "";
1851
1895
  let n = this.visibleColumns.slice(t.startCol, t.endCol + 1), r = [];
1852
1896
  for (let e = t.startRow; e <= t.endRow; e++) r.push(this.data[this._toDataIndex(e)]);
1853
- return B(r, n, e);
1897
+ return z(r, n, e);
1854
1898
  }
1855
- return B(this._sortedIndices.map((e) => this.data[e]), this.visibleColumns, e);
1899
+ return z(this._sortedIndices.map((e) => this.data[e]), this.visibleColumns, e);
1856
1900
  }
1857
1901
  exportToFile(e, t) {
1858
- G(this.exportToString(e), t ?? `export${le(e)}`, ce(e));
1902
+ W(this.exportToString(e), t ?? `export${ue(e)}`, le(e));
1859
1903
  }
1860
1904
  setComment(e, t, n) {
1861
1905
  let r = this.getComment(e, t);
@@ -1912,7 +1956,7 @@ var Y = {
1912
1956
  async importFromFile(e) {
1913
1957
  let t = e.name.toLowerCase();
1914
1958
  if (t.endsWith(".xlsx")) {
1915
- let t = await be(await e.arrayBuffer());
1959
+ let t = await xe(await e.arrayBuffer());
1916
1960
  this._applyImportedSheet(t);
1917
1961
  } else if (t.endsWith(".csv") || t.endsWith(".tsv")) {
1918
1962
  let t = O(await e.text());
@@ -2004,8 +2048,8 @@ var Y = {
2004
2048
  return this._columnWidths.get(e);
2005
2049
  }
2006
2050
  _getColWidth(e) {
2007
- let t = this._columnWidths.get(e.key) ?? e.width ?? X;
2008
- return Math.max(t, e.minWidth ?? Z);
2051
+ let t = this._columnWidths.get(e.key) ?? e.width ?? Y;
2052
+ return Math.max(t, e.minWidth ?? X);
2009
2053
  }
2010
2054
  get headerHeight() {
2011
2055
  return this.rowHeight + 8;
@@ -2036,7 +2080,15 @@ var Y = {
2036
2080
  super.disconnectedCallback(), this.removeEventListener("scroll", this._onScroll), this.removeEventListener("keydown", this._onKeyDown), this.removeEventListener("contextmenu", this._onContextMenu), this.removeEventListener("dragover", this._onDragover), this.removeEventListener("dragleave", this._onDragleave), this.removeEventListener("drop", this._onDrop), document.removeEventListener("click", this._onDocumentClick), document.removeEventListener("mousedown", this._onCommentPopupOutsideClick), this._isDragging = !1, this._wasDrag = !1, this._resizeCleanup &&= (this._resizeCleanup(), null), this._hostResizeObserver &&= (this._hostResizeObserver.disconnect(), null);
2037
2081
  }
2038
2082
  firstUpdated() {
2039
- this._measureViewport();
2083
+ this._readDensityTokens(), this._measureViewport();
2084
+ }
2085
+ _readDensityTokens() {
2086
+ let e = getComputedStyle(this).getPropertyValue("--ft-row-height").trim();
2087
+ if (!e) return;
2088
+ let t = parseFloat(e);
2089
+ if (!Number.isFinite(t) || t <= 0 || !/px\s*$/.test(e) || t === this._rowHeightFromCss) return;
2090
+ let n = this.rowHeight;
2091
+ this._rowHeightFromCss = t, this._rowHeightExplicit === void 0 && this.requestUpdate("rowHeight", n);
2040
2092
  }
2041
2093
  _updateColOffsets() {
2042
2094
  let e = this.visibleColumns, t = [], n = this._prefixWidth;
@@ -2364,7 +2416,7 @@ var Y = {
2364
2416
  "y"
2365
2417
  ].includes(t) && this._handleCtrlKey(e)) return;
2366
2418
  }
2367
- if (!(t.length === 0 || this._visibleRowCount === 0)) {
2419
+ if (t.length !== 0 && this._visibleRowCount !== 0) {
2368
2420
  if ((e.key === "Enter" || e.key === "F2") && this._activeCell) {
2369
2421
  e.preventDefault(), this._startEdit();
2370
2422
  return;
@@ -2418,7 +2470,7 @@ var Y = {
2418
2470
  e.preventDefault();
2419
2471
  let n = t[this._activeCell.col];
2420
2472
  if (!n) return !0;
2421
- let r = this._columnWidths.get(n.key) ?? n.width ?? X, i = e.key === "ArrowRight" ? 20 : -20, a = n.minWidth ?? Z, o = Math.max(a, r + i);
2473
+ let r = this._columnWidths.get(n.key) ?? n.width ?? Y, i = e.key === "ArrowRight" ? 20 : -20, a = n.minWidth ?? X, o = Math.max(a, r + i);
2422
2474
  return this._columnWidths.set(n.key, o), this.requestUpdate(), this.dispatchEvent(new CustomEvent("column-resize", {
2423
2475
  detail: {
2424
2476
  key: n.key,
@@ -2825,9 +2877,9 @@ var Y = {
2825
2877
  if (!o) return r;
2826
2878
  let s = this.data[t]?.[o.key], c = this._filters.some((e) => e.key === o.key), l = () => {
2827
2879
  this._bodyContextMenu = null;
2828
- };
2880
+ }, u = i + 200 > window.innerWidth ? i - 200 : i, d = a + 280 > window.innerHeight ? a - 280 : a;
2829
2881
  return n`
2830
- <div class="ft-body-context-menu" style="position: fixed; left: ${i + 200 > window.innerWidth ? i - 200 : i}px; top: ${a + 280 > window.innerHeight ? a - 280 : a}px; z-index: 200;"
2882
+ <div class="ft-body-context-menu" style="position: fixed; left: ${u}px; top: ${d}px; z-index: 200;"
2831
2883
  @mousedown=${(e) => e.stopPropagation()}>
2832
2884
  <div class="ft-context-menu-item"
2833
2885
  @click=${() => {
@@ -2928,10 +2980,10 @@ var Y = {
2928
2980
  }
2929
2981
  _renderCommentPopup() {
2930
2982
  if (!this._commentPopup) return r;
2931
- let { x: e, y: t } = this._commentPopup;
2983
+ let { x: e, y: t } = this._commentPopup, i = e + 240 > window.innerWidth ? e - 240 : e, a = t + 150 > window.innerHeight ? t - 150 : t;
2932
2984
  return n`
2933
2985
  <div class="ft-comment-popup"
2934
- style="position: fixed; left: ${e + 240 > window.innerWidth ? e - 240 : e}px; top: ${t + 150 > window.innerHeight ? t - 150 : t}px; z-index: 201;"
2986
+ style="position: fixed; left: ${i}px; top: ${a}px; z-index: 201;"
2935
2987
  @mousedown=${(e) => e.stopPropagation()}>
2936
2988
  <textarea class="ft-comment-popup-textarea"
2937
2989
  rows="4"
@@ -3007,11 +3059,12 @@ var Y = {
3007
3059
  return e === "empty" ? (e) => e == null || e === "" : (e) => e != null && e !== "";
3008
3060
  }
3009
3061
  _renderEmptyFilterRow(e, t) {
3062
+ let r = this._emptyFilterState.get(e) ?? "";
3010
3063
  return n`
3011
3064
  <div class="ft-filter-empty-row">
3012
3065
  <label>Blank cells</label>
3013
3066
  <select class="ft-filter-mode-select"
3014
- .value=${this._emptyFilterState.get(e) ?? ""}
3067
+ .value=${r}
3015
3068
  @change=${(n) => {
3016
3069
  let r = n.target.value;
3017
3070
  (r === "" || r === "empty" || r === "non-empty") && (r ? this._emptyFilterState.set(e, r) : this._emptyFilterState.delete(e), t?.(), this._applyFilterForKey(e));
@@ -3152,7 +3205,7 @@ var Y = {
3152
3205
  @keydown=${(e) => {
3153
3206
  e.key === "Escape" && (this._openFilterKey = null), e.stopPropagation();
3154
3207
  }}>
3155
- ${Object.keys(Y).map((e) => n`<option value=${e}>${Y[e]}</option>`)}
3208
+ ${Object.keys(J).map((e) => n`<option value=${e}>${J[e]}</option>`)}
3156
3209
  </select>
3157
3210
  <input class="ft-filter-input ft-num-cond-input" type="number" placeholder="Value"
3158
3211
  .value=${r.value == null ? "" : String(r.value)}
@@ -3263,7 +3316,7 @@ var Y = {
3263
3316
  if (!n) return;
3264
3317
  let r = this.shadowRoot?.querySelectorAll(`.ft-cell[data-col-index="${t}"]`), i = 0, a = this.shadowRoot?.querySelector(`.ft-header-cell[data-col-index="${t}"]`);
3265
3318
  if (a && (i = Math.max(i, a.scrollWidth)), r) for (let e of r) i = Math.max(i, e.scrollWidth);
3266
- let o = n.minWidth ?? Z, s = Math.max(o, i + 8);
3319
+ let o = n.minWidth ?? X, s = Math.max(o, i + 8);
3267
3320
  this._columnWidths.set(n.key, s), this.requestUpdate(), this.dispatchEvent(new CustomEvent("column-resize", {
3268
3321
  detail: {
3269
3322
  key: n.key,
@@ -3276,7 +3329,7 @@ var Y = {
3276
3329
  }
3277
3330
  _onResizeStart(e, t) {
3278
3331
  e.preventDefault(), e.stopPropagation();
3279
- let n = this.visibleColumns[t], r = this._columnWidths.get(n.key) ?? n.width ?? X;
3332
+ let n = this.visibleColumns[t], r = this._columnWidths.get(n.key) ?? n.width ?? Y;
3280
3333
  this._resizing = {
3281
3334
  colIndex: t,
3282
3335
  startX: e.clientX,
@@ -3284,13 +3337,13 @@ var Y = {
3284
3337
  };
3285
3338
  let i = (e) => {
3286
3339
  if (!this._resizing) return;
3287
- let t = e.clientX - this._resizing.startX, r = n.minWidth ?? Z, i = Math.max(r, this._resizing.startWidth + t);
3340
+ let t = e.clientX - this._resizing.startX, r = n.minWidth ?? X, i = Math.max(r, this._resizing.startWidth + t);
3288
3341
  this._columnWidths.set(n.key, i), this.requestUpdate();
3289
3342
  }, a = () => {
3290
3343
  document.removeEventListener("mousemove", i), document.removeEventListener("mouseup", o), this._resizeCleanup = null;
3291
3344
  }, o = () => {
3292
3345
  if (a(), this._resizing) {
3293
- let e = this._columnWidths.get(n.key) ?? X;
3346
+ let e = this._columnWidths.get(n.key) ?? Y;
3294
3347
  this.dispatchEvent(new CustomEvent("column-resize", {
3295
3348
  detail: {
3296
3349
  key: n.key,
@@ -3456,8 +3509,10 @@ var Y = {
3456
3509
  let t = this.visibleColumns;
3457
3510
  if (e.endCol >= t.length || e.endRow >= this._visibleRowCount) return "";
3458
3511
  let r = (this._colLeftOffsets[e.endCol] ?? 0) + this._getColWidth(t[e.endCol]) - this._scrollLeft - 4, i = this.headerHeight + (e.endRow + 1) * this.rowHeight - this._scrollTop - 4;
3459
- return r < 0 || i < this.headerHeight ? "" : n`
3460
- ${this._fillDrag?.active && this._fillDrag.targetRange ? this._renderFillPreview(this._fillDrag.targetRange) : ""}
3512
+ if (r < 0 || i < this.headerHeight) return "";
3513
+ let a = this._fillDrag?.active && this._fillDrag.targetRange ? this._renderFillPreview(this._fillDrag.targetRange) : "";
3514
+ return n`
3515
+ ${a}
3461
3516
  <div class="ft-fill-handle"
3462
3517
  style="left:${r}px;top:${i}px;"
3463
3518
  @mousedown=${(e) => this._onFillHandleMouseDown(e)}>
@@ -3466,7 +3521,9 @@ var Y = {
3466
3521
  }
3467
3522
  _renderFillPreview(e) {
3468
3523
  let t = this.visibleColumns;
3469
- return e.endCol >= t.length || e.endRow >= this._visibleRowCount ? "" : n`<div class="ft-fill-preview" style="left:${(this._colLeftOffsets[e.startCol] ?? 0) - this._scrollLeft}px;top:${this.headerHeight + e.startRow * this.rowHeight - this._scrollTop}px;width:${(this._colLeftOffsets[e.endCol] ?? 0) + this._getColWidth(t[e.endCol]) - (this._colLeftOffsets[e.startCol] ?? 0)}px;height:${(e.endRow - e.startRow + 1) * this.rowHeight}px;"></div>`;
3524
+ if (e.endCol >= t.length || e.endRow >= this._visibleRowCount) return "";
3525
+ let r = (this._colLeftOffsets[e.startCol] ?? 0) - this._scrollLeft, i = this.headerHeight + e.startRow * this.rowHeight - this._scrollTop, a = (this._colLeftOffsets[e.endCol] ?? 0) + this._getColWidth(t[e.endCol]) - (this._colLeftOffsets[e.startCol] ?? 0), o = (e.endRow - e.startRow + 1) * this.rowHeight;
3526
+ return n`<div class="ft-fill-preview" style="left:${r}px;top:${i}px;width:${a}px;height:${o}px;"></div>`;
3470
3527
  }
3471
3528
  _onRowNumMouseDown(e, t) {
3472
3529
  if (e.button !== 0) return;
@@ -3716,15 +3773,18 @@ var Y = {
3716
3773
  for (let t of l) m.push(this._renderHeaderCell(e[t], t));
3717
3774
  for (let t = s; t < c; t++) m.push(this._renderHeaderCell(e[t], t));
3718
3775
  let h = this._colDragIndicatorLeft == null ? "" : n`<div class="ft-drop-indicator" style="left:${this._colDragIndicatorLeft}px"></div>`;
3719
- if (this.data.length === 0 || this._visibleRowCount === 0) return n`
3776
+ if (this.data.length === 0 || this._visibleRowCount === 0) {
3777
+ let e = this.data.length === 0 ? "No data" : "No matching data";
3778
+ return n`
3720
3779
  ${i}
3721
3780
  <div class="ft-header" role="row" style="width: ${o}px; height: ${a}px;">
3722
3781
  ${f}${p}
3723
3782
  ${m}
3724
3783
  ${h}
3725
3784
  </div>
3726
- <div class="ft-empty">${this.data.length === 0 ? "No data" : "No matching data"}</div>
3785
+ <div class="ft-empty">${e}</div>
3727
3786
  `;
3787
+ }
3728
3788
  let { start: g, end: _ } = this.visibleRange, v = this._frozenRowCount, y = [];
3729
3789
  for (let e = g; e < _; e++) y.push(this._renderRow(e, s, c, l, (e - v) * this.rowHeight));
3730
3790
  let b = this._renderFillHandle(), x = this._rowDragIndicatorY == null ? "" : n`<div class="ft-row-drop-indicator" style="top:${this._rowDragIndicatorY}px;width:${o + this._prefixWidth}px;"></div>`;
@@ -3875,18 +3935,24 @@ var Y = {
3875
3935
  @keydown=${this._onEditorKeyDown}
3876
3936
  @blur=${() => this._commitEdit()}>
3877
3937
  `;
3878
- if (t.type === "date") return n`
3938
+ if (t.type === "date") {
3939
+ let e = this._toDateInputValue(i);
3940
+ return n`
3879
3941
  <input class="ft-editor" type="date"
3880
- .value=${this._toDateInputValue(i)}
3942
+ .value=${e}
3881
3943
  @keydown=${this._onEditorKeyDown}
3882
3944
  @blur=${() => this._commitEdit()}>
3883
3945
  `;
3884
- if (t.type === "datetime") return n`
3946
+ }
3947
+ if (t.type === "datetime") {
3948
+ let e = this._toDateTimeInputValue(i);
3949
+ return n`
3885
3950
  <input class="ft-editor" type="datetime-local"
3886
- .value=${this._toDateTimeInputValue(i)}
3951
+ .value=${e}
3887
3952
  @keydown=${this._onEditorKeyDown}
3888
3953
  @blur=${() => this._commitEdit()}>
3889
3954
  `;
3955
+ }
3890
3956
  if (t.type === "select" && t.options && t.options.length > 0) {
3891
3957
  let e = t.options, r = typeof e[0] == "string";
3892
3958
  return n`
@@ -3938,54 +4004,54 @@ var Y = {
3938
4004
  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")}`;
3939
4005
  }
3940
4006
  };
3941
- J([a({ type: Array })], $.prototype, "columns", void 0), J([a({
4007
+ q([a({ type: Array })], $.prototype, "columns", void 0), q([a({
3942
4008
  type: Array,
3943
4009
  hasChanged: () => !0
3944
- })], $.prototype, "data", null), J([a({
4010
+ })], $.prototype, "data", null), q([a({
3945
4011
  type: Boolean,
3946
4012
  attribute: "clear-undo-on-data-change"
3947
- })], $.prototype, "clearUndoOnDataChange", void 0), J([a({
4013
+ })], $.prototype, "clearUndoOnDataChange", void 0), q([a({
3948
4014
  type: Boolean,
3949
4015
  attribute: "clear-selection-on-data-change"
3950
- })], $.prototype, "clearSelectionOnDataChange", void 0), J([a({
4016
+ })], $.prototype, "clearSelectionOnDataChange", void 0), q([a({
3951
4017
  type: Number,
3952
4018
  attribute: "row-height"
3953
- })], $.prototype, "rowHeight", void 0), J([a({
4019
+ })], $.prototype, "rowHeight", null), q([a({
3954
4020
  type: Boolean,
3955
4021
  attribute: "show-row-numbers"
3956
- })], $.prototype, "showRowNumbers", void 0), J([a({
4022
+ })], $.prototype, "showRowNumbers", void 0), q([a({
3957
4023
  type: String,
3958
4024
  reflect: !0
3959
- })], $.prototype, "theme", void 0), J([a({
4025
+ })], $.prototype, "theme", void 0), q([a({
3960
4026
  type: Number,
3961
4027
  attribute: "max-rows"
3962
- })], $.prototype, "maxRows", void 0), J([a({ type: Boolean })], $.prototype, "editable", void 0), J([a({
4028
+ })], $.prototype, "maxRows", void 0), q([a({ type: Boolean })], $.prototype, "editable", void 0), q([a({
3963
4029
  type: Boolean,
3964
4030
  attribute: "show-filters"
3965
- })], $.prototype, "showFilters", void 0), J([a({
4031
+ })], $.prototype, "showFilters", void 0), q([a({
3966
4032
  type: Boolean,
3967
4033
  attribute: "show-context-menu"
3968
- })], $.prototype, "showContextMenu", void 0), J([a({
4034
+ })], $.prototype, "showContextMenu", void 0), q([a({
3969
4035
  type: Number,
3970
4036
  attribute: "frozen-rows"
3971
- })], $.prototype, "frozenRows", void 0), J([a({
4037
+ })], $.prototype, "frozenRows", void 0), q([a({
3972
4038
  type: Boolean,
3973
4039
  reflect: !0
3974
- })], $.prototype, "loading", void 0), J([a({
4040
+ })], $.prototype, "loading", void 0), q([a({
3975
4041
  type: Boolean,
3976
4042
  attribute: "import-enabled"
3977
- })], $.prototype, "importEnabled", void 0), J([a({ type: Boolean })], $.prototype, "selectable", void 0), J([a({
4043
+ })], $.prototype, "importEnabled", void 0), q([a({ type: Boolean })], $.prototype, "selectable", void 0), q([a({
3978
4044
  type: String,
3979
4045
  attribute: "selection-mode"
3980
- })], $.prototype, "selectionMode", null), J([a({
4046
+ })], $.prototype, "selectionMode", null), q([a({
3981
4047
  type: String,
3982
4048
  attribute: "data-mode"
3983
- })], $.prototype, "dataMode", void 0), J([a({
4049
+ })], $.prototype, "dataMode", void 0), q([a({
3984
4050
  type: Object,
3985
4051
  attribute: "footer-data"
3986
- })], $.prototype, "footerData", void 0), J([a({
4052
+ })], $.prototype, "footerData", void 0), q([a({
3987
4053
  type: Number,
3988
4054
  attribute: "max-undo-size"
3989
- })], $.prototype, "maxUndoSize", null), J([o()], $.prototype, "_scrollTop", void 0), J([o()], $.prototype, "_scrollLeft", void 0), J([o()], $.prototype, "_viewportHeight", void 0), J([o()], $.prototype, "_viewportWidth", void 0), J([o()], $.prototype, "_activeCell", void 0), J([o()], $.prototype, "_editingCell", void 0), J([o()], $.prototype, "_sortCriteria", void 0), J([o()], $.prototype, "_openFilterKey", void 0), J([o()], $.prototype, "_rowSelectionVersion", void 0), J([o()], $.prototype, "_autocompleteState", void 0), J([o()], $.prototype, "_headerMenu", void 0), J([o()], $.prototype, "_bodyContextMenu", void 0), J([o()], $.prototype, "_commentPopup", void 0), J([o()], $.prototype, "_isDragOver", void 0), $ = J([i("flex-table")], $);
4055
+ })], $.prototype, "maxUndoSize", null), q([o()], $.prototype, "_scrollTop", void 0), q([o()], $.prototype, "_scrollLeft", void 0), q([o()], $.prototype, "_viewportHeight", void 0), q([o()], $.prototype, "_viewportWidth", void 0), q([o()], $.prototype, "_activeCell", void 0), q([o()], $.prototype, "_editingCell", void 0), q([o()], $.prototype, "_sortCriteria", void 0), q([o()], $.prototype, "_openFilterKey", void 0), q([o()], $.prototype, "_rowSelectionVersion", void 0), q([o()], $.prototype, "_autocompleteState", void 0), q([o()], $.prototype, "_headerMenu", void 0), q([o()], $.prototype, "_bodyContextMenu", void 0), q([o()], $.prototype, "_commentPopup", void 0), q([o()], $.prototype, "_isDragOver", void 0), $ = q([i("flex-table")], $);
3990
4056
  //#endregion
3991
- export { f as a, x as i, B as n, D as r, $ as t };
4057
+ export { f as a, x as i, z as n, D as r, $ as t };
@@ -35,7 +35,23 @@ export declare class FlexTable extends LitElement {
35
35
  * (e.g. server-mode grids refreshed via `useODataSource`).
36
36
  */
37
37
  clearSelectionOnDataChange: boolean;
38
- rowHeight: number;
38
+ /**
39
+ * 행 높이(px).
40
+ *
41
+ * ⚠**이 표는 가상 스크롤이라 행 높이가 CSS 가 아니라 여기서 정해진다** — 행은
42
+ * `index * rowHeight` 로 절대 배치되고 셀 높이도 인라인으로 박힌다. 그래서 셀 여백을
43
+ * 아무리 조절해도 행 높이는 따라오지 않는다.
44
+ *
45
+ * 소비자가 표를 **문서 스코프 한 규칙**으로 맞출 수 있도록 CSS 토큰
46
+ * `--ft-row-height` 도 읽는다. 우선순위는 **프로퍼티/속성 > CSS 토큰 > 32px** 이다 —
47
+ * 명시적으로 지정한 쪽이 항상 이긴다.
48
+ */
49
+ get rowHeight(): number;
50
+ set rowHeight(value: number);
51
+ /** 프로퍼티·속성으로 **명시 지정된** 값. 지정 전에는 undefined 라 CSS 가 이긴다. */
52
+ private _rowHeightExplicit?;
53
+ /** `--ft-row-height` 판독 결과. 판독 전·판독 실패 시 기본값. */
54
+ private _rowHeightFromCss;
39
55
  showRowNumbers: boolean;
40
56
  theme: 'light' | 'dark' | undefined;
41
57
  maxRows: number;
@@ -256,6 +272,14 @@ export declare class FlexTable extends LitElement {
256
272
  connectedCallback(): void;
257
273
  disconnectedCallback(): void;
258
274
  protected firstUpdated(): void;
275
+ /**
276
+ * `--ft-row-height` 를 판독해 가상화 계산에 넣는다.
277
+ *
278
+ * ⚠**`_measureViewport` 안에 두지 않는다** — 그쪽은 ResizeObserver 가 호스트 크기가
279
+ * 바뀔 때마다 부른다. `getComputedStyle` 은 그 빈도로 돌릴 것이 아니다. 이 토큰은
280
+ * 문서 스코프의 정적 선언으로 쓰이므로 첫 렌더에 한 번 읽으면 충분하다.
281
+ */
282
+ private _readDensityTokens;
259
283
  private _updateColOffsets;
260
284
  private get visibleColRange();
261
285
  protected willUpdate(changedProperties: Map<string, unknown>): void;
@@ -1,2 +1,2 @@
1
- import { a as e, i as t, n, r, t as i } from "./flex-table-DNUTTZ1m.js";
1
+ import { a as e, i as t, n, r, t as i } from "./flex-table-DReUAeUh.js";
2
2
  export { i as FlexTable, t as RowSelectionState, r as UndoStack, n as exportData, e as renderCell };
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as e } from "./flex-table-DNUTTZ1m.js";
1
+ import { t as e } from "./flex-table-DReUAeUh.js";
2
2
  import t from "react";
3
3
  import { createComponent as n } from "@lit/react";
4
4
  var r = n({
@@ -0,0 +1 @@
1
+ import '../flex-table';
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iyulab/flex-table",
3
- "version": "0.21.1",
3
+ "version": "0.23.0",
4
4
  "description": "A minimalist, input-centric data grid web component",
5
5
  "type": "module",
6
6
  "main": "./dist/flex-table.js",
@@ -69,6 +69,7 @@
69
69
  },
70
70
  "devDependencies": {
71
71
  "@lit/react": "^1.0.8",
72
+ "@types/node": "^26.1.1",
72
73
  "@types/react": "^19.2.17",
73
74
  "@typescript-eslint/eslint-plugin": "^8.64.0",
74
75
  "@typescript-eslint/parser": "^8.64.0",