@iyulab/data-components 0.1.6 → 0.1.7

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,4 +1,12 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [0.1.7] - 2026-03-08
4
+ ### Added
5
+ - USimpleSheet: `compute` 콜백을 통한 열 단위 자동 계산 기능
6
+ - 가로(같은 행) 및 세로(행 간) 계산 모두 지원
7
+ - compute 열 자동 readonly 및 시각적 구분 (이탤릭 + 파란 배경)
8
+ - 에러 발생 시 빈 문자열 표시
9
+ - 붙여넣기/Fill 시 compute 열 건너뛰기
10
+
11
+ ## [0.1.6]
4
12
  - Initial library version release
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  **[Live Demo](https://iyulab.github.io/node-data-components/)**
6
6
 
7
- - **USimpleSheet** — 엑셀 호환 스프레드시트 입력 컴포넌트 (Lit)
7
+ - **USimpleSheet** — 엑셀 호환 스프레드시트 입력 컴포넌트 (Lit, compute 자동 계산 지원)
8
8
  - **UDataView** — Grid / List / Table 뷰 전환 컴포넌트 (Lit)
9
9
  - **UDataGrid** — OData 기반 서버 사이드 데이터 그리드 (React + DevExtreme)
10
10
 
@@ -44,6 +44,25 @@ npm install @iyulab/data-components
44
44
  ></u-simple-sheet>
45
45
  ```
46
46
 
47
+ 자동 계산 열 (compute):
48
+
49
+ ```html
50
+ <u-simple-sheet
51
+ .columns=${[
52
+ { key: 'item', label: '품목', width: 150 },
53
+ { key: 'qty', label: '수량', width: 80 },
54
+ { key: 'price', label: '단가', width: 100 },
55
+ { key: 'total', label: '합계', width: 100,
56
+ compute: (r, data) => {
57
+ const qty = Number(data[r][1]) || 0;
58
+ const price = Number(data[r][2]) || 0;
59
+ return String(qty * price);
60
+ }
61
+ },
62
+ ]}
63
+ ></u-simple-sheet>
64
+ ```
65
+
47
66
  ### UDataView
48
67
 
49
68
  ```html
@@ -13,6 +13,8 @@ export interface SheetColumn {
13
13
  options?: string[] | ((row: number, col: number) => string[]);
14
14
  /** true이면 목록에 있는 값만 입력 가능 (기본: false = 자유 입력 허용) */
15
15
  strict?: boolean;
16
+ /** 자동 계산 함수. 설정 시 해당 열은 자동 readonly. 열 순서(좌→우), 행 순서(위→아래)로 계산. */
17
+ compute?: (rowIndex: number, data: string[][]) => string;
16
18
  }
17
19
  /**
18
20
  * USimpleSheet - 심플 스프레드시트 컴포넌트
@@ -120,6 +122,9 @@ export declare class USimpleSheet extends UElement {
120
122
  private _scrollCellIntoView;
121
123
  private _getCellFromEvent;
122
124
  private _isColReadonly;
125
+ private _isColComputed;
126
+ /** compute 열을 재계산 (열 순서 좌→우, 행 순서 위→아래) */
127
+ private _recompute;
123
128
  private _getColOptions;
124
129
  private _filterOptions;
125
130
  private _emitChange;
@@ -477,6 +477,7 @@ const _USimpleSheet = class _USimpleSheet extends UElement {
477
477
  newData.push(row);
478
478
  }
479
479
  this._data = newData;
480
+ this._recompute();
480
481
  this._history = [this._data.map((r) => [...r])];
481
482
  this._historyIndex = 0;
482
483
  const prevWidths = this._colWidths;
@@ -510,6 +511,7 @@ const _USimpleSheet = class _USimpleSheet extends UElement {
510
511
  if (this._historyIndex <= 0) return;
511
512
  this._historyIndex--;
512
513
  this._data = this._history[this._historyIndex].map((r) => [...r]);
514
+ this._recompute();
513
515
  this._emitChange();
514
516
  this.requestUpdate();
515
517
  }
@@ -517,6 +519,7 @@ const _USimpleSheet = class _USimpleSheet extends UElement {
517
519
  if (this._historyIndex >= this._history.length - 1) return;
518
520
  this._historyIndex++;
519
521
  this._data = this._history[this._historyIndex].map((r) => [...r]);
522
+ this._recompute();
520
523
  this._emitChange();
521
524
  this.requestUpdate();
522
525
  }
@@ -619,11 +622,15 @@ const _USimpleSheet = class _USimpleSheet extends UElement {
619
622
  const isSelected = this._inSel(r, c);
620
623
  const isAnchor = this._isAnchor(r, c);
621
624
  const value = this._data[r]?.[c] ?? "";
625
+ const isColReadonly = this._isColReadonly(c);
626
+ const isComputed = this._isColComputed(c);
622
627
  const classes = [
623
628
  "cell",
624
629
  isSelected ? "selected" : "",
625
630
  isAnchor ? "anchor" : "",
626
- isEditing ? "editing" : ""
631
+ isEditing ? "editing" : "",
632
+ isColReadonly ? "cell-readonly" : "",
633
+ isComputed ? "cell-computed" : ""
627
634
  ].filter(Boolean).join(" ");
628
635
  const hasOptions = isEditing && this._getColOptions(r, c) !== null;
629
636
  const showDropdown = isEditing && this._dropdownItems.length > 0;
@@ -694,12 +701,13 @@ const _USimpleSheet = class _USimpleSheet extends UElement {
694
701
  for (let ci = 0; ci < pasteRows[ri].length; ci++) {
695
702
  const r = anchor.row + ri;
696
703
  const c = anchor.col + ci;
697
- if (r < newData.length && c < (newData[r]?.length ?? 0)) {
704
+ if (r < newData.length && c < (newData[r]?.length ?? 0) && !this._isColComputed(c)) {
698
705
  newData[r][c] = pasteRows[ri][ci];
699
706
  }
700
707
  }
701
708
  }
702
709
  this._data = newData;
710
+ this._recompute();
703
711
  this._pushHistory();
704
712
  this._emitChange();
705
713
  this.requestUpdate();
@@ -722,6 +730,7 @@ const _USimpleSheet = class _USimpleSheet extends UElement {
722
730
  }
723
731
  }
724
732
  this._data = newData;
733
+ this._recompute();
725
734
  this._pushHistory();
726
735
  this._emitChange();
727
736
  this.requestUpdate();
@@ -741,6 +750,7 @@ const _USimpleSheet = class _USimpleSheet extends UElement {
741
750
  }
742
751
  }
743
752
  this._data = newData;
753
+ this._recompute();
744
754
  this._pushHistory();
745
755
  this._emitChange();
746
756
  this.requestUpdate();
@@ -802,6 +812,7 @@ const _USimpleSheet = class _USimpleSheet extends UElement {
802
812
  this._dropdownItems = [];
803
813
  this._dropdownIndex = -1;
804
814
  if (this._editVal !== prevVal) {
815
+ this._recompute();
805
816
  this._pushHistory();
806
817
  this._emitChange();
807
818
  }
@@ -825,6 +836,7 @@ const _USimpleSheet = class _USimpleSheet extends UElement {
825
836
  }
826
837
  }
827
838
  this._data = newData;
839
+ this._recompute();
828
840
  this._pushHistory();
829
841
  this._emitChange();
830
842
  this.requestUpdate();
@@ -854,7 +866,25 @@ const _USimpleSheet = class _USimpleSheet extends UElement {
854
866
  return { row, col };
855
867
  }
856
868
  _isColReadonly(col) {
857
- return this.columns?.[col]?.readonly ?? false;
869
+ return (this.columns?.[col]?.readonly ?? false) || this._isColComputed(col);
870
+ }
871
+ _isColComputed(col) {
872
+ return typeof this.columns?.[col]?.compute === "function";
873
+ }
874
+ /** compute 열을 재계산 (열 순서 좌→우, 행 순서 위→아래) */
875
+ _recompute() {
876
+ if (!this.columns?.some((c) => c.compute)) return;
877
+ for (let c = 0; c < this._colCount; c++) {
878
+ const fn = this.columns?.[c]?.compute;
879
+ if (!fn) continue;
880
+ for (let r = 0; r < this._rowCount; r++) {
881
+ try {
882
+ this._data[r][c] = fn(r, this._data);
883
+ } catch {
884
+ this._data[r][c] = "";
885
+ }
886
+ }
887
+ }
858
888
  }
859
889
  _getColOptions(row, col) {
860
890
  const colDef = this.columns?.[col];
@@ -904,6 +934,7 @@ const _USimpleSheet = class _USimpleSheet extends UElement {
904
934
  const newData = this._data.map((r) => [...r]);
905
935
  newData[row][col] = value;
906
936
  this._data = newData;
937
+ this._recompute();
907
938
  this._pushHistory();
908
939
  this._emitChange();
909
940
  this.requestUpdate();
@@ -240,7 +240,38 @@ const styles = css`
240
240
  font-style: italic;
241
241
  }
242
242
 
243
- /* Readonly mode */
243
+ /* Readonly column/cell */
244
+ .cell.cell-readonly {
245
+ background: var(--u-neutral-50, #f8fafc);
246
+ color: var(--u-txt-color-weak, #64748b);
247
+ cursor: default;
248
+ }
249
+
250
+ .cell.cell-readonly.selected {
251
+ background: var(--u-neutral-100, #f1f5f9);
252
+ }
253
+
254
+ .cell.cell-readonly.anchor {
255
+ background: var(--u-neutral-50, #f8fafc);
256
+ }
257
+
258
+ /* Computed column/cell */
259
+ .cell.cell-computed {
260
+ background: var(--u-blue-50, #eff6ff);
261
+ color: var(--u-txt-color, #0f172a);
262
+ font-style: italic;
263
+ cursor: default;
264
+ }
265
+
266
+ .cell.cell-computed.selected {
267
+ background: var(--u-blue-100, #dbeafe);
268
+ }
269
+
270
+ .cell.cell-computed.anchor {
271
+ background: var(--u-blue-50, #eff6ff);
272
+ }
273
+
274
+ /* Readonly mode (whole sheet) */
244
275
  :host([readonly]) .cell {
245
276
  cursor: default;
246
277
  }
@@ -297,6 +328,32 @@ const styles = css`
297
328
  border-bottom-color: var(--u-border-color-weak, #2A2A2A);
298
329
  }
299
330
 
331
+ :host-context([theme="dark"]) .cell.cell-readonly {
332
+ background: var(--u-neutral-100, #1E1E1E);
333
+ color: var(--u-txt-color-weak, #8A8A8A);
334
+ }
335
+
336
+ :host-context([theme="dark"]) .cell.cell-readonly.selected {
337
+ background: var(--u-neutral-200, #2A2A2A);
338
+ }
339
+
340
+ :host-context([theme="dark"]) .cell.cell-readonly.anchor {
341
+ background: var(--u-neutral-100, #1E1E1E);
342
+ }
343
+
344
+ :host-context([theme="dark"]) .cell.cell-computed {
345
+ background: var(--u-blue-100, #1e3a5f);
346
+ color: var(--u-txt-color, #D4D4D4);
347
+ }
348
+
349
+ :host-context([theme="dark"]) .cell.cell-computed.selected {
350
+ background: var(--u-blue-200, #2a4a6f);
351
+ }
352
+
353
+ :host-context([theme="dark"]) .cell.cell-computed.anchor {
354
+ background: var(--u-blue-100, #1e3a5f);
355
+ }
356
+
300
357
  :host-context([theme="dark"]) .cell.selected {
301
358
  background: var(--u-blue-0, #0a1e3d);
302
359
  }
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.1.6",
4
+ "version": "0.1.7",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "web-components",