@iyulab/data-components 0.10.0 → 0.11.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,70 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.11.0] - 2026-08-04
4
+
5
+ ### Changed
6
+
7
+ - **`u-rich-table` 의 `_id` 부재 경고가 영어로 이주했다** — 개발자 대면 진단이므로 로케일
8
+ 레지스트리에 넣지 않는다(사용자가 읽는 문자열이 아니다).
9
+
10
+ - ⚠**표 문구가 «영어 기본 + 로케일 레지스트리»로 이주했다** — 기본값 7건이 한국어 리터럴이었다
11
+ (`u-rich-table` 6 · `u-simple-sheet` 1).
12
+
13
+ ```ts
14
+ import { Locale } from '@iyulab/components';
15
+ import { messages } from '@iyulab/data-components';
16
+
17
+ Locale.set('ko'); // 검증 메시지와 함께 전환된다
18
+ messages.register('ja', { empty: 'データなし' }); // 언어를 더하거나 문구를 덮는다
19
+ ```
20
+
21
+ ⚠**한국어 환경은 종전과 같은 문구를 본다**(내장 `ko` · `ko-KR` → `ko`).
22
+ ⚠**소비자가 준 값은 여전히 이긴다** — `emptyMessage` 등 프로퍼티 계약은 그대로다.
23
+
24
+ ★**기본값을 «비우고» 렌더에서 해석한다.** 프로퍼티 초기값에 문자열을 박으면 **생성 시점의
25
+ 로케일에 고정**돼 나중에 `Locale.set()` 을 불러도 이미 만들어진 표는 따라오지 않는다.
26
+ ⚠**따라서 프로퍼티를 읽으면 기본 상태에서 `''` 가 나온다**(종전에는 한국어 리터럴).
27
+ 화면 문구는 렌더 결과가 계약이다.
28
+
29
+ ★검증 메시지(`필수 항목입니다`)는 **`@iyulab/components` 의 키셋**을 쓴다
30
+ (`Locale.getValue('valueMissing')` · 14로케일). 같은 문장을 두 곳에 두지 않는다.
31
+
32
+ ### Requires
33
+
34
+ - `@iyulab/components >= 1.23.0` (`Locale.namespace`).
35
+
36
+ ### Added
37
+
38
+ - **`u-simple-sheet` 의 행 높이·여백·글자 크기를 소비자가 정할 수 있다** — 조절점 6종.
39
+
40
+ ```
41
+ --dc-row-height 24px 셀 height · line-height (둘 다)
42
+ --dc-cell-padding-block 0px 셀 · 편집 입력의 세로 여백 (행 높이에 가산)
43
+ --dc-cell-padding-inline 6px 〃 가로 여백
44
+ --dc-font-size 13px 본문 셀 · 편집 입력 · 드롭다운 항목
45
+ --dc-header-font-size 12px 열 머리
46
+ --dc-header-font-weight 600 〃
47
+ ```
48
+
49
+ 종전에는 치수가 전부 리터럴이라, 한 화면에 표가 여럿일 때 **시트만 다른 높이로 남아도
50
+ 맞출 방법이 없었다**(공개 API 에 치수 노브 0건 · `part` 0건). 0.10.0 이 연 색 축과 같은
51
+ 자리이고, 색을 맞추고 나서야 높이 차이가 드러난 형태다.
52
+
53
+ ⚠**행 높이와 `line-height` 는 한 토큰이 정한다.** 따로 열면 소비자가 한쪽만 바꿔 글자가
54
+ 세로로 어긋난다 — 그 묶임이 이 축의 유일한 제약이다.
55
+
56
+ ⚠**색 축과 달리 역할 토큰에서 파생하지 않는다** — 역할 층에 치수 축이 없다. 그래서
57
+ `:host` 리터럴 기본값이고, 소비자는 **요소 선택자**로 덮는다(`:root` 는 닿지 않는다).
58
+
59
+ ⚠**기본 렌더는 변하지 않는다.** 선언하지 않으면 종전 값으로 그려진다.
60
+
61
+ ⚠**선언값과 읽히는 행 높이는 1.5px 다르다** — `border-collapse` 로 접힌 테두리가 사용값에
62
+ 더해진다(`24px` 선언 → `25.5px` 로 읽힘). 32px 로 맞추려면 `30.5px` 를 선언한다.
63
+
64
+ `tests/browser/simple-sheet-density.browser.test.ts` 가 **실제 크로미움 계산값으로** 효력을
65
+ 잰다 — 소스 대조는 «적혀 있음»만 증명하고, 문서 스코프 선언이 섀도의 `:host` 를 이기는지는
66
+ 캐스케이드 문제라 계산값으로만 갈린다.
67
+
3
68
  ## [0.10.0] - 2026-08-03
4
69
 
5
70
  ### Added
package/README.md CHANGED
@@ -190,6 +190,40 @@ u-rich-table { --dc-icon-color: #B0B0B0; }
190
190
  ⚠**`0.10.0` 은 조절점만 엽니다 — 기본 렌더는 변하지 않습니다.** 다섯 단이 전부
191
191
  `--u-txt-color-weak` 에서 파생되며, 다크 대응도 그 역할 토큰이 이미 갖고 있습니다.
192
192
 
193
+ ### 시트 밀도·타이포 조절 (`u-simple-sheet` · 0.11.0~)
194
+
195
+ 한 화면에 표가 여럿일 때 **행 높이를 서로 맞추기 위한** 조절점입니다. 색 축(위)과 같은
196
+ 이유로 열렸습니다 — 어느 높이가 옳은지는 소비자가 정할 일인데, 종전에는 치수가 전부
197
+ 리터럴이라 정할 방법이 없었습니다.
198
+
199
+ | 토큰 | 기본값 | 무엇을 정하나 |
200
+ |---|---|---|
201
+ | `--dc-row-height` | `24px` | 셀 `height` 와 `line-height` **둘 다** |
202
+ | `--dc-cell-padding-block` | `0px` | 셀·편집 입력의 세로 여백 (행 높이에 **가산**) |
203
+ | `--dc-cell-padding-inline` | `6px` | 〃 가로 여백 |
204
+ | `--dc-font-size` | `13px` | 본문 셀 · 편집 입력 · 드롭다운 항목 |
205
+ | `--dc-header-font-size` | `12px` | 열 머리 |
206
+ | `--dc-header-font-weight` | `600` | 〃 |
207
+
208
+ ```css
209
+ /* ⚠요소 선택자로 겨눕니다 — :root 는 닿지 않습니다(아래 참조) */
210
+ u-simple-sheet {
211
+ --dc-row-height: 30.5px; /* 읽히는 행 높이 32px */
212
+ }
213
+ ```
214
+
215
+ ⚠**`:root` 로는 닿지 않습니다.** 커스텀 프로퍼티 상속값은 섀도 루트의 `:host` 선언에
216
+ 집니다. 색 축과 달리 이 여섯은 `:host` 에 리터럴 기본값을 갖고 있으므로(역할 층에 치수
217
+ 축이 없어 파생할 곳이 없습니다) **요소 선택자**(`u-simple-sheet { … }`)로 선언하십시오.
218
+
219
+ ⚠**선언값과 «읽히는» 행 높이는 1.5px 다릅니다.** `border-collapse` 로 접힌 테두리가
220
+ 사용값에 더해집니다 — 기본 `24px` 선언이 `25.5px` 로 읽힙니다. 32px 로 맞추려면
221
+ `30.5px` 를 선언하십시오. (같은 이유로 **빈 행은 데이터 행보다 1.5px 낮습니다** —
222
+ `0.11.0` 이전부터 그랬고, 교정하면 기본 렌더가 바뀌므로 이 릴리스에서는 두었습니다.)
223
+
224
+ ⚠**`0.11.0` 도 조절점만 엽니다** — 선언하지 않으면 종전과 같은 값으로 그려집니다.
225
+ 이 여섯은 `u-simple-sheet` 전용입니다(`u-data-view`·`u-rich-table` 은 읽지 않습니다).
226
+
193
227
  ### 알려진 제약
194
228
 
195
229
  - **표면 높이를 뜻하는 토큰이 없습니다** — 배경 역할 토큰은 상호작용 상태 이름
@@ -1,5 +1,6 @@
1
1
  import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
2
  import __decorate from "../../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
3
+ import { messages } from "../../utilities/messages.js";
3
4
  import { styles } from "./USimpleSheet.styles.js";
4
5
  import { html } from "lit";
5
6
  import { customElement, property, state } from "lit/decorators.js";
@@ -25,7 +26,7 @@ var USimpleSheet = class USimpleSheet extends UElement {
25
26
  this.rows = 20;
26
27
  this.cols = 10;
27
28
  this.readonly = false;
28
- this.noMatchMessage = "일치하는 항목 없음";
29
+ this.noMatchMessage = "";
29
30
  this._sel = null;
30
31
  this._editing = null;
31
32
  this._editVal = "";
@@ -612,7 +613,7 @@ var USimpleSheet = class USimpleSheet extends UElement {
612
613
  </div>
613
614
  ` : noMatch && isStrict ? html`
614
615
  <div class="cell-dropdown">
615
- <div class="dropdown-empty">${this.noMatchMessage}</div>
616
+ <div class="dropdown-empty">${this.noMatchMessage || messages.text("noMatch")}</div>
616
617
  </div>
617
618
  ` : ""}
618
619
  ` : this._formatValue(value, c, r)}
@@ -18,6 +18,25 @@ var baseStyles = css`
18
18
  --dc-header-color: var(--u-txt-color-weak, #757575); /* 열 머리 · 행 머리 */
19
19
  --dc-empty-color: var(--u-txt-color-weak, #757575); /* 빈 상태 안내문 */
20
20
  --dc-readonly-color: var(--u-txt-color-weak, #757575); /* 읽기 전용 셀 */
21
+
22
+ /* ── 소비자 조절점 — 밀도·타이포 ──
23
+ ★색 축(위)과 같은 이유로 열었다. 한 제품 안에 표가 여럿일 때 «어느 높이가 옳은가»는
24
+ 소비자가 정할 일인데, 이 컴포넌트는 치수가 전부 리터럴이라 정할 방법이 없었다.
25
+ ⚠**행 높이와 line-height 는 한 토큰이 정한다.** 따로 열면 소비자가 한쪽만 바꿔
26
+ 글자가 세로로 어긋난다 — 그 묶임이 이 축의 유일한 제약이다.
27
+ ⚠**여백 축은 «가산»이다**: 실제 행 높이 = --dc-row-height + 2×--dc-cell-padding-block.
28
+ 기본값이 0 이라 선언하지 않으면 종전과 같다.
29
+ ⚠**색 축과 달리 역할 토큰에서 파생하지 않는다** — 역할 층에 치수 축이 없다.
30
+ 그래서 폴백이 아니라 :host 리터럴 기본값이고, 소비자는 **요소 선택자**로 덮는다
31
+ (:host 선언은 상속값을 이기므로 :root 로는 닿지 않는다).
32
+ ⚠**이 컴포넌트가 «읽는» 단만 선언한다** — 색 축과 같은 규칙이다. */
33
+ --dc-row-height: 24px; /* 셀 height · line-height (묶임) */
34
+ --dc-cell-padding-block: 0px; /* 셀 · 편집 입력의 세로 여백 (가산) */
35
+ --dc-cell-padding-inline: 6px; /* 〃 가로 여백 */
36
+ --dc-font-size: 13px; /* 본문 셀 · 편집 입력 · 드롭다운 항목 */
37
+ --dc-header-font-size: 12px; /* 열 머리 */
38
+ --dc-header-font-weight: 600; /* 〃 */
39
+
21
40
  display: block;
22
41
  width: 100%;
23
42
  height: 400px;
@@ -78,8 +97,8 @@ var baseStyles = css`
78
97
  background: var(--u-neutral-100, #F5F5F5);
79
98
  padding: 4px 8px;
80
99
  text-align: center;
81
- font-size: 12px;
82
- font-weight: 600;
100
+ font-size: var(--dc-header-font-size);
101
+ font-weight: var(--dc-header-font-weight);
83
102
  color: var(--dc-header-color);
84
103
  border-right: 1px solid var(--u-border-color, #E0E0E0);
85
104
  border-bottom: 2px solid var(--u-border-color, #E0E0E0);
@@ -160,18 +179,20 @@ var baseStyles = css`
160
179
 
161
180
  /* Data cells */
162
181
  .cell {
163
- padding: 0 6px;
182
+ padding: var(--dc-cell-padding-block) var(--dc-cell-padding-inline);
164
183
  border-right: 1px solid var(--u-border-color-weak, #EEEEEE);
165
184
  border-bottom: 1px solid var(--u-border-color-weak, #EEEEEE);
166
- font-size: 13px;
185
+ font-size: var(--dc-font-size);
167
186
  color: var(--u-txt-color, #212121);
168
187
  min-width: 80px;
169
188
  overflow: hidden;
170
189
  text-overflow: ellipsis;
171
190
  white-space: nowrap;
172
191
  cursor: cell;
173
- height: 24px;
174
- line-height: 24px;
192
+ /* ⚠height 는 «빈 셀»을 정한다 — 내용이 있는 셀의 높이는 line-height 가 만든다.
193
+ 중 하나만 배선하면 데이터 행과 빈 행의 높이가 갈린다(네거티브 컨트롤이 잡은 자리). */
194
+ height: var(--dc-row-height);
195
+ line-height: var(--dc-row-height);
175
196
  vertical-align: middle;
176
197
  position: relative;
177
198
  box-sizing: border-box;
@@ -206,8 +227,9 @@ var baseStyles = css`
206
227
  border: none;
207
228
  outline: 2px solid var(--u-blue-500, #2196F3);
208
229
  outline-offset: -1px;
209
- padding: 0 6px;
210
- font-size: 13px;
230
+ /* ⚠셀과 «같은 값»이어야 한다 — 어긋나면 편집 진입 순간 글자가 튄다. */
231
+ padding: var(--dc-cell-padding-block) var(--dc-cell-padding-inline);
232
+ font-size: var(--dc-font-size);
211
233
  font-family: inherit;
212
234
  color: var(--u-txt-color, #212121);
213
235
  background: var(--u-bg-color, #FFFFFF);
@@ -235,7 +257,8 @@ var baseStyles = css`
235
257
 
236
258
  .dropdown-item {
237
259
  padding: 4px 8px;
238
- font-size: 13px;
260
+ /* 셀 값의 후보를 보이는 자리다 — 본문 글자 축을 따른다. */
261
+ font-size: var(--dc-font-size);
239
262
  color: var(--u-txt-color, #212121);
240
263
  cursor: pointer;
241
264
  white-space: nowrap;
@@ -1,9 +1,11 @@
1
1
  import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
2
  import __decorate from "../../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
3
+ import { messages } from "../../utilities/messages.js";
3
4
  import { richTableStyles } from "./styles.js";
4
5
  import { parseTSV, toTSV } from "./utils/clipboard.js";
5
6
  import { LitElement, html } from "lit";
6
7
  import { customElement, property, state } from "lit/decorators.js";
8
+ import { Locale } from "@iyulab/components/dist/utilities/Locale.js";
7
9
  //#region src/components/u-rich-table/URichTable.ts
8
10
  var URichTable = class URichTable extends LitElement {
9
11
  constructor(..._args) {
@@ -14,12 +16,16 @@ var URichTable = class URichTable extends LitElement {
14
16
  this.pageSize = 25;
15
17
  this.currentPage = 1;
16
18
  this.loading = false;
17
- this.emptyMessage = "데이터가 없습니다";
18
- this.loadingMessage = "로딩 중...";
19
- this.filterPlaceholder = "필터...";
20
- this.filterAllLabel = "전체";
21
- this.addRowLabel = "+ 새 행";
22
- this.pageInfoFormatter = (total, start, end) => `전체 ${total.toLocaleString()}건 중 ${start}-${end} 표시`;
19
+ this.emptyMessage = "";
20
+ this.loadingMessage = "";
21
+ this.filterPlaceholder = "";
22
+ this.filterAllLabel = "";
23
+ this.addRowLabel = "";
24
+ this.pageInfoFormatter = (total, start, end) => messages.text("pageInfo", {
25
+ total: total.toLocaleString(),
26
+ start,
27
+ end
28
+ });
23
29
  this.selectable = false;
24
30
  this.editable = false;
25
31
  this.addable = false;
@@ -113,7 +119,7 @@ var URichTable = class URichTable extends LitElement {
113
119
  _warnMissingRowId() {
114
120
  if (this._warnedMissingRowId) return;
115
121
  this._warnedMissingRowId = true;
116
- console.warn("[@iyulab/data-components] u-rich-table: 행에 `_id` 없어 **위치**로 식별합니다. 데이터가 재정렬·재페이징되면 선택 상태가 다른 행으로 옮겨갑니다. 행에 고유한 `_id` 를 부여하세요.");
122
+ console.warn("[@iyulab/data-components] u-rich-table: rows have no `_id`, so selection is tracked by **position**. Re-sorting or re-paging the data moves the selection to a different row. Give every row a unique `_id`.");
117
123
  }
118
124
  getSelectedRows() {
119
125
  return this.data.filter((row, i) => this.selectedIds.has(this._rowId(row, i)));
@@ -149,7 +155,7 @@ var URichTable = class URichTable extends LitElement {
149
155
  <div style="flex:1"></div>
150
156
  <slot name="toolbar-end"></slot>
151
157
  ${this.addable ? html`
152
- <button class="btn btn-success" @click=${this._onAddRowClick}>${this.addRowLabel}</button>
158
+ <button class="btn btn-success" @click=${this._onAddRowClick}>${this.addRowLabel || messages.text("addRow")}</button>
153
159
  ` : ""}
154
160
  </div>
155
161
  `;
@@ -184,10 +190,10 @@ var URichTable = class URichTable extends LitElement {
184
190
  ${this.columns.map((col) => html`
185
191
  <td>
186
192
  ${col.filterable !== false ? col.filterType === "select" && col.options ? html`<select @change=${(e) => this._onFilterChange(col.key, e.target.value)}>
187
- <option value="">${this.filterAllLabel}</option>
193
+ <option value="">${this.filterAllLabel || messages.text("filterAll")}</option>
188
194
  ${col.options.map((o) => html`<option value=${o.value}>${o.label}</option>`)}
189
195
  </select>` : html`<input
190
- placeholder=${this.filterPlaceholder}
196
+ placeholder=${this.filterPlaceholder || messages.text("filterPlaceholder")}
191
197
  @input=${(e) => this._onFilterChange(col.key, e.target.value)} />` : ""}
192
198
  </td>
193
199
  `)}
@@ -196,8 +202,8 @@ var URichTable = class URichTable extends LitElement {
196
202
  `;
197
203
  }
198
204
  _renderBody() {
199
- if (this.loading) return html`<tr><td colspan=${this._colSpan()}><div class="loading-overlay">${this.loadingMessage}</div></td></tr>`;
200
- if (this.data.length === 0) return html`<tr><td colspan=${this._colSpan()}><div class="empty-message">${this.emptyMessage}</div></td></tr>`;
205
+ if (this.loading) return html`<tr><td colspan=${this._colSpan()}><div class="loading-overlay">${this.loadingMessage || messages.text("loading")}</div></td></tr>`;
206
+ if (this.data.length === 0) return html`<tr><td colspan=${this._colSpan()}><div class="empty-message">${this.emptyMessage || messages.text("empty")}</div></td></tr>`;
201
207
  return this.data.map((row, rowIdx) => {
202
208
  const rowId = this._rowId(row, rowIdx);
203
209
  const isSelected = this.selectedIds.has(rowId);
@@ -438,7 +444,7 @@ var URichTable = class URichTable extends LitElement {
438
444
  let newValue = this.editValue;
439
445
  if (col.type === "number") newValue = Number(newValue);
440
446
  if (col.required && !newValue && newValue !== 0) {
441
- this.validationErrors = new Map(this.validationErrors).set(`${rowIndex}-${colIndex}`, "필수 항목입니다");
447
+ this.validationErrors = new Map(this.validationErrors).set(`${rowIndex}-${colIndex}`, Locale.getValue("valueMissing"));
442
448
  return;
443
449
  }
444
450
  if (col.validator) {
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `@iyulab/data-components` 의 화면 문자열 — **영어 기본 + 로케일 레지스트리**.
3
+ *
4
+ * ## 왜 생겼나
5
+ *
6
+ * 표 컴포넌트의 기본 문구가 **한국어 리터럴**이었다(실측 7건). 소비자가 `emptyMessage` 등을
7
+ * 넘겨 덮을 수는 있었지만, **덮지 않으면 한국어가 나온다** — 로케일 표준(영어 기본 +
8
+ * 레지스트리)을 채택한 리포에서 그것은 미이주다.
9
+ *
10
+ * ## 기본값을 «비워» 두고 렌더에서 해석한다
11
+ *
12
+ * 프로퍼티 초기값으로 문자열을 박으면 **생성 시점의 로케일에 고정**된다 — `Locale.set()` 을
13
+ * 나중에 부르면 이미 만들어진 표는 따라오지 않는다. 그래서 기본값은 빈 문자열이고,
14
+ * 렌더가 `this.x || messages.text(...)` 로 해석한다.
15
+ *
16
+ * ⚠**소비자가 덮는 경로는 그대로다** — 값을 주면 그 값이 이긴다.
17
+ *
18
+ * ## 검증 메시지는 여기 두지 않는다
19
+ *
20
+ * `필수 항목입니다` 는 `@iyulab/components` 의 **검증 메시지 키셋에 이미 있고 14로케일이
21
+ * 딸려 온다**(`Locale.getValue('valueMissing')`). 같은 문장을 이 표에 복제하면 두 곳이
22
+ * 갈라진다.
23
+ */
24
+ export type DataMessageKey = 'empty' | 'loading' | 'filterPlaceholder' | 'filterAll' | 'addRow' | 'pageInfo' | 'noMatch';
25
+ export declare const messages: import('@iyulab/components/dist/utilities/Locale.js').LocaleNamespace<DataMessageKey>;
@@ -0,0 +1,23 @@
1
+ import { Locale } from "@iyulab/components/dist/utilities/Locale.js";
2
+ //#region src/utilities/messages.ts
3
+ var messages = Locale.namespace("@iyulab/data-components");
4
+ messages.register("en", {
5
+ empty: "No data",
6
+ loading: "Loading…",
7
+ filterPlaceholder: "Filter…",
8
+ filterAll: "All",
9
+ addRow: "+ New row",
10
+ pageInfo: "Showing {start}-{end} of {total}",
11
+ noMatch: "No matching item"
12
+ });
13
+ messages.register("ko", {
14
+ empty: "데이터가 없습니다",
15
+ loading: "로딩 중...",
16
+ filterPlaceholder: "필터...",
17
+ filterAll: "전체",
18
+ addRow: "+ 새 행",
19
+ pageInfo: "전체 {total}건 중 {start}-{end} 표시",
20
+ noMatch: "일치하는 항목 없음"
21
+ });
22
+ //#endregion
23
+ export { messages };
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.10.0",
4
+ "version": "0.11.0",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "web-components",
@@ -58,7 +58,7 @@
58
58
  "lint:fix": "eslint src/ --fix"
59
59
  },
60
60
  "dependencies": {
61
- "@iyulab/components": "^1.18.0",
61
+ "@iyulab/components": "^1.23.0",
62
62
  "lit": "^3.3.3"
63
63
  },
64
64
  "peerDependencies": {