@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
|
-
## [
|
|
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
|
|
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
|
}
|