@iyulab/data-components 0.1.7 → 0.3.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.
@@ -0,0 +1,433 @@
1
+ import { css } from 'lit';
2
+
3
+ const richTableStyles = css`
4
+ :host {
5
+ display: block;
6
+ font-family: system-ui, -apple-system, sans-serif;
7
+ font-size: 13px;
8
+ border: 1px solid #e2e8f0;
9
+ border-radius: 8px;
10
+ overflow: hidden;
11
+ }
12
+
13
+ .toolbar {
14
+ display: flex;
15
+ align-items: center;
16
+ gap: 8px;
17
+ padding: 8px 12px;
18
+ background: #f8fafc;
19
+ border-bottom: 1px solid #e2e8f0;
20
+ }
21
+
22
+ .toolbar .selection-info {
23
+ font-size: 12px;
24
+ color: #64748b;
25
+ }
26
+
27
+ .toolbar .search-input {
28
+ padding: 4px 10px;
29
+ border: 1px solid #d1d5db;
30
+ border-radius: 4px;
31
+ font-size: 12px;
32
+ outline: none;
33
+ }
34
+
35
+ .toolbar .search-input:focus {
36
+ border-color: #3b82f6;
37
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
38
+ }
39
+
40
+ .toolbar .btn {
41
+ padding: 4px 10px;
42
+ border: none;
43
+ border-radius: 4px;
44
+ font-size: 12px;
45
+ cursor: pointer;
46
+ color: white;
47
+ }
48
+
49
+ .toolbar .btn-primary { background: #3b82f6; }
50
+ .toolbar .btn-primary:hover { background: #2563eb; }
51
+ .toolbar .btn-success { background: #10b981; }
52
+ .toolbar .btn-success:hover { background: #059669; }
53
+
54
+ table {
55
+ width: 100%;
56
+ border-collapse: collapse;
57
+ }
58
+
59
+ thead th {
60
+ padding: 8px;
61
+ background: #f1f5f9;
62
+ font-weight: 600;
63
+ text-align: left;
64
+ border-bottom: 2px solid #e2e8f0;
65
+ user-select: none;
66
+ position: relative;
67
+ }
68
+
69
+ thead th.sortable {
70
+ cursor: pointer;
71
+ }
72
+
73
+ thead th.sortable:hover {
74
+ background: #e2e8f0;
75
+ }
76
+
77
+ .sort-indicator {
78
+ font-size: 10px;
79
+ color: #94a3b8;
80
+ margin-left: 4px;
81
+ }
82
+
83
+ .filter-row td {
84
+ padding: 4px;
85
+ background: #fefce8;
86
+ border-bottom: 1px solid #e2e8f0;
87
+ }
88
+
89
+ .filter-row input,
90
+ .filter-row select {
91
+ width: 100%;
92
+ padding: 2px 6px;
93
+ border: 1px solid #d1d5db;
94
+ border-radius: 3px;
95
+ font-size: 11px;
96
+ outline: none;
97
+ }
98
+
99
+ tbody tr {
100
+ border-bottom: 1px solid #e2e8f0;
101
+ }
102
+
103
+ tbody tr:hover {
104
+ background: #f8fafc;
105
+ }
106
+
107
+ tbody tr.selected {
108
+ background: #eff6ff;
109
+ }
110
+
111
+ tbody tr.focused td.focused-cell {
112
+ outline: 2px solid #3b82f6;
113
+ outline-offset: -2px;
114
+ }
115
+
116
+ tbody tr.editing {
117
+ background: #fefce8;
118
+ outline: 2px solid #eab308;
119
+ }
120
+
121
+ tbody tr.error {
122
+ background: #fef2f2;
123
+ }
124
+
125
+ tbody td {
126
+ padding: 8px;
127
+ }
128
+
129
+ tbody td .cell-edit-input {
130
+ width: 100%;
131
+ padding: 4px 6px;
132
+ border: 1px solid #3b82f6;
133
+ border-radius: 3px;
134
+ font-size: 13px;
135
+ outline: none;
136
+ }
137
+
138
+ tbody td .cell-edit-input.invalid {
139
+ border-color: #ef4444;
140
+ }
141
+
142
+ .validation-error {
143
+ font-size: 10px;
144
+ color: #ef4444;
145
+ margin-top: 2px;
146
+ }
147
+
148
+ .checkbox-cell {
149
+ width: 40px;
150
+ text-align: center;
151
+ }
152
+
153
+ .expand-cell {
154
+ width: 30px;
155
+ text-align: center;
156
+ cursor: pointer;
157
+ color: #94a3b8;
158
+ }
159
+
160
+ .expand-cell:hover {
161
+ color: #3b82f6;
162
+ }
163
+
164
+ .actions-cell {
165
+ width: 60px;
166
+ text-align: center;
167
+ }
168
+
169
+ .new-row td {
170
+ padding: 4px 8px;
171
+ background: #f0fdf4;
172
+ opacity: 0.8;
173
+ }
174
+
175
+ .new-row input {
176
+ width: 100%;
177
+ padding: 4px 6px;
178
+ border: 1px dashed #86efac;
179
+ border-radius: 3px;
180
+ background: transparent;
181
+ font-size: 12px;
182
+ color: #6b7280;
183
+ outline: none;
184
+ }
185
+
186
+ .new-row input:focus {
187
+ border-color: #10b981;
188
+ background: white;
189
+ opacity: 1;
190
+ }
191
+
192
+ .detail-row td {
193
+ padding: 8px 8px 8px 70px;
194
+ background: #f8fafc;
195
+ border-bottom: 2px solid #e2e8f0;
196
+ }
197
+
198
+ .badge {
199
+ display: inline-block;
200
+ padding: 2px 8px;
201
+ border-radius: 10px;
202
+ font-size: 11px;
203
+ font-weight: 500;
204
+ }
205
+
206
+ .pagination {
207
+ display: flex;
208
+ justify-content: space-between;
209
+ align-items: center;
210
+ padding: 8px 12px;
211
+ background: #f8fafc;
212
+ border-top: 1px solid #e2e8f0;
213
+ font-size: 12px;
214
+ color: #64748b;
215
+ }
216
+
217
+ .pagination .page-buttons {
218
+ display: flex;
219
+ gap: 4px;
220
+ align-items: center;
221
+ }
222
+
223
+ .pagination button {
224
+ padding: 2px 8px;
225
+ border: 1px solid #d1d5db;
226
+ border-radius: 3px;
227
+ background: white;
228
+ font-size: 11px;
229
+ cursor: pointer;
230
+ }
231
+
232
+ .pagination button.active {
233
+ background: #3b82f6;
234
+ color: white;
235
+ border-color: #3b82f6;
236
+ }
237
+
238
+ .pagination select {
239
+ padding: 2px 4px;
240
+ border: 1px solid #d1d5db;
241
+ border-radius: 3px;
242
+ font-size: 11px;
243
+ margin-left: 8px;
244
+ }
245
+
246
+ .loading-overlay {
247
+ display: flex;
248
+ align-items: center;
249
+ justify-content: center;
250
+ padding: 40px;
251
+ color: #6b7280;
252
+ }
253
+
254
+ .empty-message {
255
+ display: flex;
256
+ align-items: center;
257
+ justify-content: center;
258
+ padding: 40px;
259
+ color: #9ca3af;
260
+ }
261
+
262
+ /* ── Dark mode ──
263
+ 외부에서 --u-* CSS 변수가 제공되면 그 값을 사용하고,
264
+ 제공되지 않으면 자체 dark fallback 값을 적용한다. */
265
+
266
+ :host-context([theme="dark"]) {
267
+ border-color: var(--u-border-color, #3D3D3D);
268
+ color: var(--u-txt-color, #D4D4D4);
269
+ }
270
+
271
+ :host-context([theme="dark"]) .toolbar {
272
+ background: var(--u-neutral-200, #1E1E1E);
273
+ border-bottom-color: var(--u-border-color, #3D3D3D);
274
+ }
275
+
276
+ :host-context([theme="dark"]) .toolbar .selection-info {
277
+ color: var(--u-txt-color-weak, #8A8A8A);
278
+ }
279
+
280
+ :host-context([theme="dark"]) .toolbar .search-input {
281
+ background: var(--u-input-bg-color, #1E1E1E);
282
+ color: var(--u-txt-color, #D4D4D4);
283
+ border-color: var(--u-input-border-color, #3D3D3D);
284
+ }
285
+
286
+ :host-context([theme="dark"]) .toolbar .search-input:focus {
287
+ border-color: var(--u-blue-500, #6ba3e3);
288
+ box-shadow: 0 0 0 2px rgba(107, 163, 227, 0.2);
289
+ }
290
+
291
+ :host-context([theme="dark"]) .toolbar .btn-primary {
292
+ background: var(--u-blue-500, #6ba3e3);
293
+ }
294
+
295
+ :host-context([theme="dark"]) .toolbar .btn-primary:hover {
296
+ background: var(--u-blue-600, #87B8F5);
297
+ }
298
+
299
+ :host-context([theme="dark"]) .toolbar .btn-success {
300
+ background: var(--u-green-500, #66B584);
301
+ }
302
+
303
+ :host-context([theme="dark"]) .toolbar .btn-success:hover {
304
+ background: var(--u-green-600, #81D19D);
305
+ }
306
+
307
+ :host-context([theme="dark"]) thead th {
308
+ background: var(--u-neutral-200, #1E1E1E);
309
+ color: var(--u-txt-color, #D4D4D4);
310
+ border-bottom-color: var(--u-border-color, #3D3D3D);
311
+ }
312
+
313
+ :host-context([theme="dark"]) thead th.sortable:hover {
314
+ background: var(--u-neutral-300, #2A2A2A);
315
+ }
316
+
317
+ :host-context([theme="dark"]) .sort-indicator {
318
+ color: var(--u-txt-color-weak, #8A8A8A);
319
+ }
320
+
321
+ :host-context([theme="dark"]) .filter-row td {
322
+ background: var(--u-yellow-0, #2E2200);
323
+ border-bottom-color: var(--u-border-color, #3D3D3D);
324
+ }
325
+
326
+ :host-context([theme="dark"]) .filter-row input,
327
+ :host-context([theme="dark"]) .filter-row select {
328
+ background: var(--u-input-bg-color, #1E1E1E);
329
+ color: var(--u-txt-color, #D4D4D4);
330
+ border-color: var(--u-input-border-color, #3D3D3D);
331
+ }
332
+
333
+ :host-context([theme="dark"]) tbody tr {
334
+ border-bottom-color: var(--u-border-color-weak, #2A2A2A);
335
+ }
336
+
337
+ :host-context([theme="dark"]) tbody tr:hover {
338
+ background: var(--u-neutral-200, #1E1E1E);
339
+ }
340
+
341
+ :host-context([theme="dark"]) tbody tr.selected {
342
+ background: var(--u-blue-0, #0a1e3d);
343
+ }
344
+
345
+ :host-context([theme="dark"]) tbody tr.focused td.focused-cell {
346
+ outline-color: var(--u-blue-500, #6ba3e3);
347
+ }
348
+
349
+ :host-context([theme="dark"]) tbody tr.editing {
350
+ background: var(--u-yellow-0, #2E2200);
351
+ outline-color: var(--u-yellow-500, #D4A712);
352
+ }
353
+
354
+ :host-context([theme="dark"]) tbody tr.error {
355
+ background: var(--u-red-0, #2E0A0A);
356
+ }
357
+
358
+ :host-context([theme="dark"]) tbody td .cell-edit-input {
359
+ background: var(--u-input-bg-color, #1E1E1E);
360
+ color: var(--u-txt-color, #D4D4D4);
361
+ border-color: var(--u-blue-500, #6ba3e3);
362
+ }
363
+
364
+ :host-context([theme="dark"]) tbody td .cell-edit-input.invalid {
365
+ border-color: var(--u-red-500, #D66060);
366
+ }
367
+
368
+ :host-context([theme="dark"]) .validation-error {
369
+ color: var(--u-red-500, #D66060);
370
+ }
371
+
372
+ :host-context([theme="dark"]) .expand-cell {
373
+ color: var(--u-txt-color-weak, #8A8A8A);
374
+ }
375
+
376
+ :host-context([theme="dark"]) .expand-cell:hover {
377
+ color: var(--u-blue-500, #6ba3e3);
378
+ }
379
+
380
+ :host-context([theme="dark"]) .new-row td {
381
+ background: var(--u-green-0, #0D2818);
382
+ }
383
+
384
+ :host-context([theme="dark"]) .new-row input {
385
+ border-color: var(--u-green-500, #66B584);
386
+ color: var(--u-txt-color-weak, #8A8A8A);
387
+ }
388
+
389
+ :host-context([theme="dark"]) .new-row input:focus {
390
+ border-color: var(--u-green-600, #81D19D);
391
+ background: var(--u-input-bg-color, #1E1E1E);
392
+ color: var(--u-txt-color, #D4D4D4);
393
+ }
394
+
395
+ :host-context([theme="dark"]) .detail-row td {
396
+ background: var(--u-neutral-200, #1E1E1E);
397
+ border-bottom-color: var(--u-border-color, #3D3D3D);
398
+ }
399
+
400
+ :host-context([theme="dark"]) .pagination {
401
+ background: var(--u-neutral-200, #1E1E1E);
402
+ border-top-color: var(--u-border-color, #3D3D3D);
403
+ color: var(--u-txt-color-weak, #8A8A8A);
404
+ }
405
+
406
+ :host-context([theme="dark"]) .pagination button {
407
+ background: var(--u-neutral-300, #2A2A2A);
408
+ color: var(--u-txt-color, #D4D4D4);
409
+ border-color: var(--u-border-color, #3D3D3D);
410
+ }
411
+
412
+ :host-context([theme="dark"]) .pagination button.active {
413
+ background: var(--u-blue-500, #6ba3e3);
414
+ color: var(--u-neutral-1000, #FFFFFF);
415
+ border-color: var(--u-blue-500, #6ba3e3);
416
+ }
417
+
418
+ :host-context([theme="dark"]) .pagination select {
419
+ background: var(--u-input-bg-color, #1E1E1E);
420
+ color: var(--u-txt-color, #D4D4D4);
421
+ border-color: var(--u-input-border-color, #3D3D3D);
422
+ }
423
+
424
+ :host-context([theme="dark"]) .loading-overlay {
425
+ color: var(--u-txt-color-weak, #8A8A8A);
426
+ }
427
+
428
+ :host-context([theme="dark"]) .empty-message {
429
+ color: var(--u-txt-color-disabled, #525252);
430
+ }
431
+ `;
432
+
433
+ export { richTableStyles };
@@ -0,0 +1,67 @@
1
+ export interface ColumnDef {
2
+ key: string;
3
+ label: string;
4
+ width?: string;
5
+ sortable?: boolean;
6
+ editable?: boolean;
7
+ required?: boolean;
8
+ type?: 'text' | 'number' | 'date' | 'select' | 'badge';
9
+ options?: {
10
+ value: string;
11
+ label: string;
12
+ }[];
13
+ badgeColors?: Record<string, string>;
14
+ render?: (value: unknown, row: Record<string, unknown>) => string | HTMLElement;
15
+ align?: 'left' | 'center' | 'right';
16
+ filterable?: boolean;
17
+ filterType?: 'text' | 'select';
18
+ validator?: (value: unknown, row: Record<string, unknown>) => string | null;
19
+ clipboardParse?: (text: string) => unknown;
20
+ clipboardFormat?: (value: unknown) => string;
21
+ }
22
+ export interface CellPosition {
23
+ rowIndex: number;
24
+ colIndex: number;
25
+ }
26
+ export interface SortState {
27
+ field: string;
28
+ direction: 'asc' | 'desc';
29
+ }
30
+ export interface FilterState {
31
+ [field: string]: string;
32
+ }
33
+ export interface RichTableEventMap {
34
+ 'selection-change': CustomEvent<{
35
+ selectedRows: Record<string, unknown>[];
36
+ }>;
37
+ 'row-create': CustomEvent<{
38
+ row: Record<string, unknown>;
39
+ }>;
40
+ 'row-update': CustomEvent<{
41
+ row: Record<string, unknown>;
42
+ field: string;
43
+ value: unknown;
44
+ oldValue: unknown;
45
+ }>;
46
+ 'row-delete': CustomEvent<{
47
+ row: Record<string, unknown>;
48
+ }>;
49
+ 'row-expand': CustomEvent<{
50
+ row: Record<string, unknown>;
51
+ expanded: boolean;
52
+ }>;
53
+ 'sort-change': CustomEvent<{
54
+ field: string;
55
+ direction: 'asc' | 'desc' | null;
56
+ }>;
57
+ 'filter-change': CustomEvent<{
58
+ filters: FilterState;
59
+ }>;
60
+ 'page-change': CustomEvent<{
61
+ page: number;
62
+ pageSize: number;
63
+ }>;
64
+ 'paste': CustomEvent<{
65
+ rows: Record<string, unknown>[];
66
+ }>;
67
+ }
@@ -0,0 +1,9 @@
1
+ import { ColumnDef } from '../types.js';
2
+ /**
3
+ * 탭/줄바꿈 구분 텍스트(TSV)를 파싱하여 행 배열로 반환
4
+ */
5
+ export declare function parseTSV(text: string, columns: ColumnDef[]): Record<string, unknown>[];
6
+ /**
7
+ * 행 배열을 탭/줄바꿈 구분 텍스트(TSV)로 변환
8
+ */
9
+ export declare function toTSV(rows: Record<string, unknown>[], columns: ColumnDef[]): string;
@@ -0,0 +1,34 @@
1
+ function parseTSV(text, columns) {
2
+ const lines = text.trim().split("\n");
3
+ return lines.map((line) => {
4
+ const cells = line.split(" ");
5
+ const row = {};
6
+ columns.forEach((col, i) => {
7
+ if (i < cells.length) {
8
+ const raw = cells[i].trim();
9
+ if (col.clipboardParse) {
10
+ row[col.key] = col.clipboardParse(raw);
11
+ } else if (col.type === "number") {
12
+ row[col.key] = Number(raw.replace(/,/g, "")) || 0;
13
+ } else {
14
+ row[col.key] = raw;
15
+ }
16
+ }
17
+ });
18
+ return row;
19
+ });
20
+ }
21
+ function toTSV(rows, columns) {
22
+ const header = columns.map((c) => c.label).join(" ");
23
+ const body = rows.map(
24
+ (row) => columns.map((col) => {
25
+ const value = row[col.key];
26
+ if (col.clipboardFormat) return col.clipboardFormat(value);
27
+ return String(value ?? "");
28
+ }).join(" ")
29
+ ).join("\n");
30
+ return `${header}
31
+ ${body}`;
32
+ }
33
+
34
+ export { parseTSV, toTSV };
package/dist/index.d.ts CHANGED
@@ -2,3 +2,4 @@ export { UDataGrid } from './components/data-grid/UDataGrid';
2
2
  export type { UDataGridColumn, UDataGridProps } from './components/data-grid/UDataGrid.types';
3
3
  export * from './components/data-view/UDataView';
4
4
  export * from './components/simple-sheet/USimpleSheet';
5
+ export * from './components/u-rich-table/URichTable';
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  export { UDataGrid } from './components/data-grid/UDataGrid.js';
2
2
  import './components/data-view/UDataView.js';
3
3
  import './components/simple-sheet/USimpleSheet.js';
4
+ import './components/u-rich-table/URichTable.js';
4
5
  export { UDataView } from './components/data-view/UDataView.component.js';
6
+ export { URichTable } from './components/u-rich-table/URichTable.component.js';
5
7
  export { USimpleSheet } from './components/simple-sheet/USimpleSheet.component.js';
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.7",
4
+ "version": "0.3.0",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "web-components",
@@ -46,6 +46,10 @@
46
46
  "./data-grid": {
47
47
  "types": "./dist/components/data-grid/UDataGrid.d.ts",
48
48
  "import": "./dist/components/data-grid/UDataGrid.js"
49
+ },
50
+ "./u-rich-table": {
51
+ "types": "./dist/components/u-rich-table/URichTable.d.ts",
52
+ "import": "./dist/components/u-rich-table/URichTable.js"
49
53
  }
50
54
  },
51
55
  "sideEffects": [
@@ -54,6 +58,7 @@
54
58
  "./dist/components/data-grid/UDataGrid.js",
55
59
  "./dist/components/data-view/UDataView.js",
56
60
  "./dist/components/simple-sheet/USimpleSheet.js",
61
+ "./dist/components/u-rich-table/URichTable.js",
57
62
  "./dist/init.js"
58
63
  ],
59
64
  "scripts": {