@iyulab/data-components 0.13.0 → 0.16.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,85 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.16.0] - 2026-08-16
4
+
5
+ ### Added
6
+
7
+ - **`u-rich-table` gained a `--dc-font-size` custom property**, defaulting to
8
+ `var(--u-density, 13px)` — the same LOB density switch `@iyulab/components`,
9
+ `@iyulab/flex-table`, and `u-simple-sheet` already read. Setting `--u-density` on an
10
+ ancestor now scales the table's header and body cell text (and the cell edit input, which
11
+ intentionally shares the same token so entering edit mode never changes the font size)
12
+ along with the rest of the UI. This component's other text (toolbar, pagination, filter
13
+ row, badges, sort indicator, validation messages) keeps its own fixed sizes — it forms a
14
+ four-step visual hierarchy (13/12/11/10px) that a single density switch would flatten, so
15
+ only the primary content axis (header/body/edit) follows it. Unset, rendering is
16
+ byte-identical to before. An explicit `u-rich-table { --dc-font-size: … }` override still
17
+ wins over the switch.
18
+
19
+ ## [0.15.0] - 2026-08-16
20
+
21
+ ### Added
22
+
23
+ - **`u-simple-sheet`'s body font size now reads `var(--u-density, 13px)`.** `@iyulab/components`
24
+ (`u-button`/`u-form`/`u-button-group`) and `@iyulab/flex-table` (`--ft-font-size`) already read
25
+ this switch; setting `--u-density` on an ancestor now scales the sheet's body text along with
26
+ them, instead of leaving it as the one stack that doesn't follow. Unset, rendering is
27
+ byte-identical to before (same 13px default). The element-selector override
28
+ (`u-simple-sheet { --dc-font-size: … }`) still wins over the switch, matching every other
29
+ token on this component. `--dc-header-font-size` is unaffected — the header and body font
30
+ sizes remain independent, as before.
31
+
32
+ ## [0.14.0] - 2026-08-07
33
+
34
+ ### Added
35
+
36
+ - **`u-rich-table` gained a `row-activate` event** — the most common grid interaction, "click a
37
+ row to see its detail," had no way to reach the app. `selection-change` is checkbox-only and
38
+ conflating it with "the row being viewed" breaks multi-select screens; `row-expand` announces
39
+ that a detail row opened but gives the app no slot to render into. Clicking a cell now always
40
+ fires `row-activate` (`editable` columns included — a single click never enters edit mode, only
41
+ `dblclick` does), and so does `Enter` on a focused cell in a non-`editable` column (an
42
+ `editable` column's `Enter` still starts editing, unchanged). `detail: { row, id, via: 'click' |
43
+ 'keyboard' }`. Purely additive — no existing behavior changes.
44
+
45
+ ### Fixed
46
+
47
+ - 🔴**`u-rich-table`: giving the host a height made the pagination unreachable.** The component
48
+ declared no vertical layout, so under a height constraint the content overflowed at its natural
49
+ size while `:host` clipped with `overflow: hidden` — and because the pagination is the last child,
50
+ it was the first thing to disappear. It stayed in the DOM; it was simply neither visible nor
51
+ scrollable to. *The table rendered, but page 2 could not be reached.*
52
+
53
+ That left consumers with two options, both of which cost something the grid is for: constrain the
54
+ height and lose paging, or leave it unconstrained and let 50 rows become a 2,000px document whose
55
+ header scrolls away.
56
+
57
+ The host is now a column flex container: the toolbar and pagination hold their positions and only
58
+ the row area scrolls. The header row is `sticky`, so column names stay put while rows scroll —
59
+ the boundary line under it is drawn with an inset `box-shadow` rather than `border-bottom`,
60
+ because under `border-collapse: collapse` cell borders belong to the table and do not follow a
61
+ stuck header.
62
+
63
+ **Unconstrained use is unchanged**: with no height, `flex: 1 1 auto` follows content height and
64
+ `overflow: auto` adds no scrollbar. Verified in a real browser at both sizes.
65
+
66
+ - 🔴**`sideEffects` omitted this package's own entry barrel, so bundlers dropped every element
67
+ registration.** The barrel that `exports["."]` resolves to exists solely to register the custom
68
+ elements, but it was not in the `sideEffects` allowlist. A consumer writing
69
+ `import '@iyulab/data-components'` — the form this package's own documentation recommends — had the module
70
+ elided entirely in a production build. The failure is silent: the build succeeds with no warning,
71
+ the tags remain in the DOM, and an unregistered custom element renders nothing.
72
+
73
+ Registration modules were already listed correctly. That was not enough: a dropped barrel means
74
+ they are never reached.
75
+
76
+ The `./react` subpath entry had the same gap. This also explains a second symptom:
77
+ `querySelector` still returns an element for an unregistered tag, so consumer code calling a
78
+ component method on it fails with `is not a function` rather than returning null.
79
+
80
+ Both the source-resolved and published-artifact forms of every affected entry point are now
81
+ declared, so workspace consumers and installed consumers get the same guarantee.
82
+
3
83
  ## [0.13.0] - 2026-08-05
4
84
 
5
85
  ### Added
package/README.md CHANGED
@@ -218,10 +218,16 @@ u-rich-table { --dc-icon-color: #B0B0B0; }
218
218
  | `--dc-row-height` | `24px` | 셀 `height` 와 `line-height` **둘 다** |
219
219
  | `--dc-cell-padding-block` | `0px` | 셀·편집 입력의 세로 여백 (행 높이에 **가산**) |
220
220
  | `--dc-cell-padding-inline` | `6px` | 〃 가로 여백 |
221
- | `--dc-font-size` | `13px` | 본문 셀 · 편집 입력 · 드롭다운 항목 |
222
- | `--dc-header-font-size` | `12px` | 열 머리 |
221
+ | `--dc-font-size` | `13px`(`var(--u-density, 13px)`) | 본문 셀 · 편집 입력 · 드롭다운 항목 |
222
+ | `--dc-header-font-size` | `12px` | 열 머리 — `--u-density` 와 무관(본문과 독립) |
223
223
  | `--dc-header-font-weight` | `600` | 〃 |
224
224
 
225
+ ⚠**`--dc-font-size` 만 예외입니다 — `@iyulab/components`/`@iyulab/flex-table` 의 밀도
226
+ 스위치(`--u-density`)를 폴백 원본으로 갖습니다**(0.15.0~). 조상에 `--u-density` 를 걸면
227
+ 본문 글자가 컨트롤·표와 함께 움직입니다. 이 요소에 `--dc-font-size` 를 직접 선언하면
228
+ 그 값이 여전히 이깁니다 — 다른 다섯 토큰과 같은 규칙입니다. 머리행은 이 스위치의
229
+ 영향을 받지 않습니다.
230
+
225
231
  ```css
226
232
  /* ⚠요소 선택자로 겨눕니다 — :root 는 닿지 않습니다(아래 참조) */
227
233
  u-simple-sheet {
@@ -230,8 +236,9 @@ u-simple-sheet {
230
236
  ```
231
237
 
232
238
  ⚠**`:root` 로는 닿지 않습니다.** 커스텀 프로퍼티 상속값은 섀도 루트의 `:host` 선언에
233
- 집니다. 색 축과 달리 이 여섯은 `:host` 리터럴 기본값을 갖고 있으므로(역할 층에 치수
234
- 축이 없어 파생할 곳이 없습니다) **요소 선택자**(`u-simple-sheet { }`)로 선언하십시오.
239
+ 집니다. 색 축과 달리 이 여섯은(`--dc-font-size` 포함 역할 토큰이 아니라 `--u-density`
240
+ 스위치를 참조할 뿐, 역할 층에서 파생하는 것은 아닙니다) `:host` 기본값을 갖고 있으므로
241
+ **요소 선택자**(`u-simple-sheet { … }`)로 선언하십시오.
235
242
 
236
243
  ⚠**선언값과 «읽히는» 행 높이는 1.5px 다릅니다.** `border-collapse` 로 접힌 테두리가
237
244
  사용값에 더해집니다 — 기본 `24px` 선언이 `25.5px` 로 읽힙니다. 32px 로 맞추려면
@@ -239,7 +246,34 @@ u-simple-sheet {
239
246
  `0.11.0` 이전부터 그랬고, 교정하면 기본 렌더가 바뀌므로 이 릴리스에서는 두었습니다.)
240
247
 
241
248
  ⚠**`0.11.0` 도 조절점만 엽니다** — 선언하지 않으면 종전과 같은 값으로 그려집니다.
242
- 이 여섯은 `u-simple-sheet` 전용입니다(`u-data-view`·`u-rich-table` 은 읽지 않습니다).
249
+ `--dc-row-height`·`--dc-cell-padding-block`·`--dc-cell-padding-inline`·
250
+ `--dc-header-font-size`·`--dc-header-font-weight` 다섯은 `u-simple-sheet` 전용입니다
251
+ (`u-data-view`·`u-rich-table` 은 읽지 않습니다). `--dc-font-size` 만 예외입니다 — 바로
252
+ 아래 `u-rich-table` 절 참조.
253
+
254
+ ### 그리드 밀도 조절 (`u-rich-table` · 0.16.0~)
255
+
256
+ 시트 밀도 축(위)과 같은 이유로, `u-rich-table` 도 `--dc-font-size` 를 갖습니다 —
257
+ `--u-density` 를 폴백 원본으로 삼는 것도 `u-simple-sheet` 와 동일합니다.
258
+
259
+ | 토큰 | 기본값 | 무엇을 정하나 |
260
+ |---|---|---|
261
+ | `--dc-font-size` | `13px`(`var(--u-density, 13px)`) | 열 머리 · 본문 셀 · 편집 입력 |
262
+
263
+ ⚠**이 컴포넌트는 시각 위계가 넉 단(13/12/11/10px)이고, 이 하나만 밀도를 따릅니다.**
264
+ 툴바·페이지네이션·필터 행·배지·정렬 표시자·검증 오류 메시지는 의도적으로 남습니다 —
265
+ 전부를 한 스위치에 묶으면 그 위계가 무너집니다. 편집 입력은 본문 셀과 **같은 토큰**을
266
+ 공유합니다 — 따로 두면 편집 진입 순간 글자가 튑니다.
267
+
268
+ ```css
269
+ /* 조상에 걸면 헤더·본문·편집 입력이 함께 움직입니다 */
270
+ .app-shell { --u-density: 15px; }
271
+
272
+ /* 이 그리드만 다르게 */
273
+ u-rich-table { --dc-font-size: 15px; }
274
+ ```
275
+
276
+ ⚠**미설정 시 렌더는 종전(`13px`)과 바이트 단위로 동일합니다.**
243
277
 
244
278
  ### 알려진 제약
245
279
 
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.142.0/helpers/esm/decorate.js
1
+ //#region \0@oxc-project+runtime@0.144.0/helpers/esm/decorate.js
2
2
  function __decorate(decorators, target, key, desc) {
3
3
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
4
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.142.0/helpers/esm/decorateMetadata.js
1
+ //#region \0@oxc-project+runtime@0.144.0/helpers/esm/decorateMetadata.js
2
2
  function __decorateMetadata(k, v) {
3
3
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
4
4
  }
@@ -1,6 +1,6 @@
1
1
  import { styles } from "./UDataView.styles.js";
2
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
3
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
2
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorateMetadata.js";
3
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorate.js";
4
4
  import { html } from "lit";
5
5
  import { customElement, property } from "lit/decorators.js";
6
6
  import "@iyulab/components/dist/components/icon/UIcon.js";
@@ -1,5 +1,5 @@
1
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorateMetadata.js";
2
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorate.js";
3
3
  import { messages } from "../../utilities/messages.js";
4
4
  import { styles } from "./USimpleSheet.styles.js";
5
5
  import { html } from "lit";
@@ -56,8 +56,10 @@ var USimpleSheet = class USimpleSheet extends UElement {
56
56
  if (e.ctrlKey || e.metaKey) {
57
57
  if (e.key === "z" || e.key === "Z") {
58
58
  e.preventDefault();
59
- if (!this.readonly) if (e.shiftKey) this._redo();
60
- else this._undo();
59
+ if (!this.readonly) {
60
+ if (e.shiftKey) this._redo();
61
+ else this._undo();
62
+ }
61
63
  return;
62
64
  }
63
65
  if (e.key === "y" || e.key === "Y") {
@@ -109,9 +111,10 @@ var USimpleSheet = class USimpleSheet extends UElement {
109
111
  }
110
112
  if (e.key === "Tab") {
111
113
  e.preventDefault();
112
- if (e.shiftKey) if (anchor.col === 0 && anchor.row > 0) this._select(anchor.row - 1, this._colCount - 1);
113
- else this._select(anchor.row, Math.max(0, anchor.col - 1));
114
- else if (anchor.col === this._colCount - 1) {
114
+ if (e.shiftKey) {
115
+ if (anchor.col === 0 && anchor.row > 0) this._select(anchor.row - 1, this._colCount - 1);
116
+ else this._select(anchor.row, Math.max(0, anchor.col - 1));
117
+ } else if (anchor.col === this._colCount - 1) {
115
118
  if (anchor.row < this._rowCount - 1) this._select(anchor.row + 1, 0);
116
119
  } else this._select(anchor.row, anchor.col + 1);
117
120
  return;
@@ -220,11 +223,13 @@ var USimpleSheet = class USimpleSheet extends UElement {
220
223
  e.preventDefault();
221
224
  const pos = this._editing;
222
225
  this._commitEdit();
223
- if (e.shiftKey) if (pos.col === 0 && pos.row > 0) this._select(pos.row - 1, this._colCount - 1);
224
- else this._select(pos.row, Math.max(0, pos.col - 1));
225
- else if (pos.col === this._colCount - 1) if (pos.row < this._rowCount - 1) this._select(pos.row + 1, 0);
226
- else this._select(pos.row, pos.col);
227
- else this._select(pos.row, pos.col + 1);
226
+ if (e.shiftKey) {
227
+ if (pos.col === 0 && pos.row > 0) this._select(pos.row - 1, this._colCount - 1);
228
+ else this._select(pos.row, Math.max(0, pos.col - 1));
229
+ } else if (pos.col === this._colCount - 1) {
230
+ if (pos.row < this._rowCount - 1) this._select(pos.row + 1, 0);
231
+ else this._select(pos.row, pos.col);
232
+ } else this._select(pos.row, pos.col + 1);
228
233
  return;
229
234
  }
230
235
  if (e.key === "ArrowUp") {
@@ -29,12 +29,19 @@ var baseStyles = css`
29
29
  ⚠**색 축과 달리 역할 토큰에서 파생하지 않는다** — 역할 층에 치수 축이 없다.
30
30
  그래서 폴백이 아니라 :host 리터럴 기본값이고, 소비자는 **요소 선택자**로 덮는다
31
31
  (:host 선언은 상속값을 이기므로 :root 로는 닿지 않는다).
32
- ⚠**이 컴포넌트가 «읽는» 단만 선언한다** — 색 축과 같은 규칙이다. */
32
+ ⚠**이 컴포넌트가 «읽는» 단만 선언한다** — 색 축과 같은 규칙이다.
33
+ ★**단 하나의 예외 — --dc-font-size 는 --u-density 를 폴백 원본으로 갖는다.**
34
+ components(UButton/UForm/UButtonGroup)와 flex-table(--ft-font-size)이
35
+ 이미 같은 형태로 이 스위치를 읽는다 — 컨트롤과 표가 «한 벌»로 움직이려면 이
36
+ 스택도 같은 원천을 봐야 한다. **--dc-header-font-size 는 여기 포함되지 않는다** —
37
+ 본문과 머리행이 서로 독립이라는 이 컴포넌트의 기존 계약(아래 회귀 테스트가
38
+ 고정)을 이 변경이 깨지 않는다. --u-density 미설정 시 렌더는 종전과 바이트
39
+ 단위로 동일하다(기본값이 같은 13px). */
33
40
  --dc-row-height: 24px; /* 셀 height · line-height (묶임) */
34
41
  --dc-cell-padding-block: 0px; /* 셀 · 편집 입력의 세로 여백 (가산) */
35
42
  --dc-cell-padding-inline: 6px; /* 〃 가로 여백 */
36
- --dc-font-size: 13px; /* 본문 셀 · 편집 입력 · 드롭다운 항목 */
37
- --dc-header-font-size: 12px; /* 열 머리 */
43
+ --dc-font-size: var(--u-density, 13px); /* 본문 셀 · 편집 입력 · 드롭다운 항목 */
44
+ --dc-header-font-size: 12px; /* 열 머리 — --u-density 와 무관(독립 계약 유지) */
38
45
  --dc-header-font-weight: 600; /* 〃 */
39
46
 
40
47
  display: block;
@@ -131,6 +131,12 @@ export declare class URichTable extends LitElement {
131
131
  private _onSortClick;
132
132
  private _onFilterChange;
133
133
  private _onCellClick;
134
+ /**
135
+ * 「선택」(체크박스)과 별개로 「보고 있는 행」을 앱에 알린다. 셀 단일 클릭은
136
+ * `editable` 열에서도 편집에 들어가지 않으므로(dblclick만 진입) 항상 안전하게 낼 수 있다.
137
+ * 키보드 경로(Enter)는 `editable` 열에서 이미 편집 진입 신호이므로 그 경우는 내지 않는다.
138
+ */
139
+ private _fireRowActivate;
134
140
  private _onCellDblClick;
135
141
  private _onEditKeyDown;
136
142
  private _onCellEditConfirm;
@@ -1,5 +1,5 @@
1
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorateMetadata.js";
2
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorate.js";
3
3
  import { messages } from "../../utilities/messages.js";
4
4
  import { richTableStyles } from "./styles.js";
5
5
  import { parseTSV, toTSV } from "./utils/clipboard.js";
@@ -73,7 +73,7 @@ var URichTable = class URichTable extends LitElement {
73
73
  if (col?.editable) {
74
74
  const value = this.data[this.focusedCell.rowIndex]?.[col.key];
75
75
  this._onCellDblClick(this.focusedCell.rowIndex, this.focusedCell.colIndex, value);
76
- }
76
+ } else this._fireRowActivate(this.focusedCell.rowIndex, "keyboard");
77
77
  }
78
78
  if (e.key === " " && this.selectable) {
79
79
  e.preventDefault();
@@ -178,14 +178,16 @@ var URichTable = class URichTable extends LitElement {
178
178
  render() {
179
179
  return html`
180
180
  ${this._renderToolbar()}
181
- <table>
182
- ${this._renderHeader()}
183
- <tbody>
184
- ${this.filterable ? this._renderFilterRow() : ""}
185
- ${this._renderBody()}
186
- ${this.addable ? this._renderNewRow() : ""}
187
- </tbody>
188
- </table>
181
+ <div class="table-wrap">
182
+ <table>
183
+ ${this._renderHeader()}
184
+ <tbody>
185
+ ${this.filterable ? this._renderFilterRow() : ""}
186
+ ${this._renderBody()}
187
+ ${this.addable ? this._renderNewRow() : ""}
188
+ </tbody>
189
+ </table>
190
+ </div>
189
191
  ${this._renderPagination()}
190
192
  `;
191
193
  }
@@ -452,12 +454,13 @@ var URichTable = class URichTable extends LitElement {
452
454
  this._fireSelectionChange();
453
455
  }
454
456
  _onSortClick(field) {
455
- if (this.sort?.field === field) if (this.sort.direction === "asc") this.sort = {
456
- field,
457
- direction: "desc"
458
- };
459
- else this.sort = null;
460
- else this.sort = {
457
+ if (this.sort?.field === field) {
458
+ if (this.sort.direction === "asc") this.sort = {
459
+ field,
460
+ direction: "desc"
461
+ };
462
+ else this.sort = null;
463
+ } else this.sort = {
461
464
  field,
462
465
  direction: "asc"
463
466
  };
@@ -494,6 +497,25 @@ var URichTable = class URichTable extends LitElement {
494
497
  colIndex: colIdx
495
498
  };
496
499
  this._lastSelectedIndex = rowIdx;
500
+ this._fireRowActivate(rowIdx, "click");
501
+ }
502
+ /**
503
+ * 「선택」(체크박스)과 별개로 「보고 있는 행」을 앱에 알린다. 셀 단일 클릭은
504
+ * `editable` 열에서도 편집에 들어가지 않으므로(dblclick만 진입) 항상 안전하게 낼 수 있다.
505
+ * 키보드 경로(Enter)는 `editable` 열에서 이미 편집 진입 신호이므로 그 경우는 내지 않는다.
506
+ */
507
+ _fireRowActivate(rowIdx, via) {
508
+ const row = this.data[rowIdx];
509
+ if (!row) return;
510
+ this.dispatchEvent(new CustomEvent("row-activate", {
511
+ detail: {
512
+ row,
513
+ id: this._rowId(row, rowIdx),
514
+ via
515
+ },
516
+ bubbles: true,
517
+ composed: true
518
+ }));
497
519
  }
498
520
  _onCellDblClick(rowIdx, colIdx, value) {
499
521
  this.editingCell = {
@@ -16,16 +16,47 @@ var richTableStyles = css`
16
16
  --dc-muted-color: var(--u-txt-color-weak, #757575); /* 보조 텍스트 · 라벨 */
17
17
  --dc-icon-color: var(--u-txt-color-weak, #757575); /* 정렬 표시 · 확장 · 행 메뉴 */
18
18
  --dc-empty-color: var(--u-txt-color-weak, #757575); /* 빈 상태 안내문 */
19
- display: block;
19
+ /* ── 소비자 조절점 — 본문 타이포 ──
20
+ ★USimpleSheet와 같은 이름 --dc-font-size 를 그대로 쓴다 — 이 패키지의 표 컴포넌트가
21
+ 공유하는 관용(위 색 축과 같은 이유: --dc-muted-color 도 세 컴포넌트가 공유한다).
22
+ components/flex-table/USimpleSheet 이 이미 읽는 --u-density 밀도 스위치를 폴백
23
+ 원본으로 가져, 컨트롤·표·시트·이 그리드가 한 벌로 움직인다.
24
+ ⚠**모든 font-size 선언이 아니라 이 하나만 연다.** 이 컴포넌트는 시각 위계가
25
+ 13(본문)·12(툴바/보조)·11(필터/페이지네이션)·10(장식적 표시자)px 넉 단으로
26
+ 이미 갈려 있다 — 전부를 하나의 스위치로 묶으면 위계가 무너진다. 여기서 여는 것은
27
+ **헤더·본문 셀**(:host 상속 경로) + **편집 입력**뿐이다. 편집 입력은 반드시 이
28
+ 토큰을 같이 읽어야 한다 — 따로 두면 편집 진입 순간 글자 크기가 튄다(USimpleSheet
29
+ 가 "본문 셀 · 편집 입력 · 드롭다운 항목"을 한 토큰으로 묶은 것과 같은 이유).
30
+ 나머지 열한 곳(툴바·페이지네이션·필터행·배지·정렬 표시자·검증 오류)은 의도적으로
31
+ 남겨 둔다 — 장식·보조 텍스트라 D-17 이 아이콘/배지 크기를 뺀 것과 같은 경계다. */
32
+ --dc-font-size: var(--u-density, 13px);
33
+ /* ── 세로 레이아웃 ──
34
+ 🔴**높이 제약은 데이터 그리드의 기본 사용 형태다** — 「조회 조건 + 결과 목록」이 한 화면에
35
+ 들어가야 하고, 결과가 몇 건이든 조건 영역과 페이지 이동은 항상 같은 자리에 있어야 한다.
36
+ 종전에는 «display: block» 이라 호스트에 높이를 주면 내용이 자연 높이로 넘쳤고,
37
+ «overflow: hidden» 때문에 **넘친 부분에 도달할 방법이 없었다.** 페이지네이션이 마지막
38
+ 자식이므로 **가장 먼저 사라진다** ⇒ *표는 그려지는데 2페이지로 갈 수 없었다.*
39
+ ⚠**높이를 주지 않는 사용은 그대로다** — «flex: 1 1 auto» 는 제약이 없으면 내용 높이를
40
+ 따르고 «overflow: auto» 는 넘치지 않으면 스크롤바를 만들지 않는다. */
41
+ display: flex;
42
+ flex-direction: column;
20
43
  font-family: system-ui, -apple-system, sans-serif;
21
- font-size: 13px;
44
+ font-size: var(--dc-font-size);
22
45
  color: var(--u-txt-color, #212121);
23
46
  border: 1px solid var(--u-border-color, #E0E0E0);
24
47
  border-radius: 8px;
25
48
  overflow: hidden;
26
49
  }
27
50
 
51
+ /* 행 영역만 스크롤한다 — 툴바와 페이지네이션은 자리를 지킨다. */
52
+ .table-wrap {
53
+ flex: 1 1 auto;
54
+ overflow: auto;
55
+ min-height: 0; /* ⚠flex 아이템의 기본 min-height:auto 는 «줄어들지 않음»이라 이것이 없으면 넘친다 */
56
+ }
57
+
28
58
  .toolbar {
59
+ flex: 0 0 auto;
29
60
  display: flex;
30
61
  align-items: center;
31
62
  gap: 8px;
@@ -79,9 +110,19 @@ var richTableStyles = css`
79
110
  background: var(--u-bg-color-raised, #FAFAFA);
80
111
  font-weight: 600;
81
112
  text-align: left;
82
- border-bottom: 2px solid var(--u-border-color, #E0E0E0);
83
113
  user-select: none;
84
- position: relative;
114
+
115
+ /* ⚠**아래 경계선을 «border-bottom» 으로 그리지 않는다.** «border-collapse: collapse» 에서
116
+ 셀 테두리는 테이블이 소유하므로 **헤더만 sticky 로 띄우면 함께 따라오지 않는다** —
117
+ 스크롤하는 순간 구분선이 사라지고 행이 헤더 밑으로 비쳐 지나간다. «box-shadow» 는
118
+ 요소가 그리므로 sticky 를 따라온다. */
119
+ box-shadow: inset 0 -2px 0 var(--u-border-color, #E0E0E0);
120
+
121
+ /* 행 영역이 스크롤되는 동안 열 이름은 남는다 — 「어느 열이 무엇인지」가 이 컴포넌트의
122
+ 스크롤 모델에서 잃으면 안 되는 것이다. */
123
+ position: sticky;
124
+ top: 0;
125
+ z-index: 1;
85
126
  }
86
127
 
87
128
  thead th.sortable {
@@ -149,7 +190,8 @@ var richTableStyles = css`
149
190
  padding: 4px 6px;
150
191
  border: 1px solid var(--u-primary-color, #1976D2);
151
192
  border-radius: 3px;
152
- font-size: 13px;
193
+ /* --dc-font-size 를 본문 셀과 공유한다 — 따로 두면 편집 진입 순간 글자가 튄다. */
194
+ font-size: var(--dc-font-size);
153
195
  outline: none;
154
196
  }
155
197
 
@@ -222,6 +264,7 @@ var richTableStyles = css`
222
264
  }
223
265
 
224
266
  .pagination {
267
+ flex: 0 0 auto;
225
268
  display: flex;
226
269
  justify-content: space-between;
227
270
  align-items: center;
@@ -66,6 +66,16 @@ export interface RichTableEventMap {
66
66
  row: Record<string, unknown>;
67
67
  expanded: boolean;
68
68
  }>;
69
+ /**
70
+ * 사용자가 행을 "열었다" — 셀 클릭 또는 포커스된 행에서의 `Enter`. `selectable` 과 무관하다:
71
+ * 선택은 "무엇을 처리할까", 활성화는 "무엇을 볼까"이다. `editable` 열에서 `Enter` 는 이미
72
+ * 셀 편집 진입 신호이므로 그 경우는 내지 않는다(`via: 'keyboard'` 는 비-editable 열에서만 발생).
73
+ */
74
+ 'row-activate': CustomEvent<{
75
+ row: Record<string, unknown>;
76
+ id: string;
77
+ via: 'click' | 'keyboard';
78
+ }>;
69
79
  'sort-change': CustomEvent<{
70
80
  field: string;
71
81
  direction: 'asc' | 'desc' | null;
package/dist/react.d.ts CHANGED
@@ -36,6 +36,11 @@ export declare const URichTableReact: import('@lit/react').ReactWebComponent<URi
36
36
  row: Record<string, unknown>;
37
37
  expanded: boolean;
38
38
  }>>;
39
+ readonly onRowActivate: EventName<CustomEvent<{
40
+ row: Record<string, unknown>;
41
+ id: string;
42
+ via: "click" | "keyboard";
43
+ }>>;
39
44
  readonly onSortChange: EventName<CustomEvent<{
40
45
  field: string;
41
46
  direction: "asc" | "desc" | null;
package/dist/react.js CHANGED
@@ -39,6 +39,7 @@ var URichTableReact = createComponent({
39
39
  onRowUpdate: "row-update",
40
40
  onRowDelete: "row-delete",
41
41
  onRowExpand: "row-expand",
42
+ onRowActivate: "row-activate",
42
43
  onSortChange: "sort-change",
43
44
  onFilterChange: "filter-change",
44
45
  onPageChange: "page-change",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@iyulab/data-components",
3
3
  "description": "iyulab data visualization components",
4
- "version": "0.13.0",
4
+ "version": "0.16.0",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "web-components",
@@ -42,16 +42,20 @@
42
42
  "./u-rich-table": "./dist/components/u-rich-table/URichTable.js"
43
43
  },
44
44
  "sideEffects": [
45
- "./src/utilities/shadowDomProtection.ts",
45
+ "./dist/components/data-view/UDataView.js",
46
46
  "./src/components/data-view/UDataView.ts",
47
+ "./dist/components/simple-sheet/USimpleSheet.js",
47
48
  "./src/components/simple-sheet/USimpleSheet.ts",
49
+ "./dist/components/u-rich-table/URichTable.js",
48
50
  "./src/components/u-rich-table/URichTable.ts",
51
+ "./dist/index.js",
52
+ "./src/index.ts",
53
+ "./dist/init.js",
49
54
  "./src/init.ts",
55
+ "./dist/react.js",
56
+ "./src/react.ts",
50
57
  "./dist/utilities/shadowDomProtection.js",
51
- "./dist/components/data-view/UDataView.js",
52
- "./dist/components/simple-sheet/USimpleSheet.js",
53
- "./dist/components/u-rich-table/URichTable.js",
54
- "./dist/init.js"
58
+ "./src/utilities/shadowDomProtection.ts"
55
59
  ],
56
60
  "scripts": {
57
61
  "start": "vite",
@@ -57,6 +57,24 @@ table.addEventListener('filter-change', e => load({ filters: e.detail.filters })
57
57
  `totalCount` is what the pager counts — not `data.length`. Setting `data` alone
58
58
  produces a table that believes it holds every matching record.
59
59
 
60
+ ## Sizing: give the host a height
61
+
62
+ The component manages its own vertical layout. Constrain the host and the row
63
+ area is what scrolls — the toolbar and the pager keep their positions, and the
64
+ header row stays visible while rows move under it:
65
+
66
+ ```css
67
+ u-rich-table { height: calc(100vh - 280px); }
68
+ ```
69
+
70
+ That is the shape a query screen wants: filters above, the pager always in the
71
+ same place, and column names readable no matter how far down you are.
72
+
73
+ Leave the height off and the table grows to its content instead — no inner
74
+ scrollbar, the page scrolls. Both are supported; pick per screen. What you
75
+ should **not** do is wrap it in your own `overflow: auto` container, which puts
76
+ the header and toolbar back inside the scrolling region.
77
+
60
78
  ## Selection across pages
61
79
 
62
80
  Two facts are deliberately separate, because in server paging they differ:
@@ -158,6 +176,7 @@ type SelectionChange = RichTableEventMap['selection-change'];
158
176
  | `row-create` | `{ row }` | The add-row control produced a row |
159
177
  | `row-delete` | `{ row }` | A row was deleted |
160
178
  | `row-expand` | `{ row, expanded }` | A detail row was opened or closed |
179
+ | `row-activate` | `{ row, id, via }` | A row was clicked, or `Enter` was pressed on a focused non-editable cell (`via` is `'click'` or `'keyboard'`). Independent of `selectable` — selection is "what to act on", activation is "what to view" |
161
180
  | `paste` | `{ rows }` | TSV was pasted into the grid |
162
181
 
163
182
  ## ColumnDef
@@ -193,7 +212,7 @@ type SelectionChange = RichTableEventMap['selection-change'];
193
212
  | Keys | Action |
194
213
  |---|---|
195
214
  | Arrow keys | Move the focused cell |
196
- | `Enter` | Start editing the focused cell / commit and move down |
215
+ | `Enter` | Editable cell: start editing / commit and move down. Non-editable cell: emit `row-activate` |
197
216
  | `Escape` | Cancel editing |
198
217
  | `Tab` | Commit and move to the next cell |
199
218
  | `Space` | Toggle selection of the focused row (when `selectable`) |