@iyulab/data-components 0.7.1 → 0.8.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 +158 -0
- package/README.md +36 -2
- package/dist/components/data-view/UDataView.styles.js +17 -90
- package/dist/components/simple-sheet/USimpleSheet.d.ts +2 -0
- package/dist/components/simple-sheet/USimpleSheet.js +3 -1
- package/dist/components/simple-sheet/USimpleSheet.styles.js +11 -107
- package/dist/components/u-rich-table/URichTable.d.ts +25 -0
- package/dist/components/u-rich-table/URichTable.js +47 -15
- package/dist/components/u-rich-table/styles.js +82 -196
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,163 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.0] - 2026-08-02
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **UDataView 가 역할 토큰만 읽는다.** 다크 전용 규칙 92줄을 제거하고, 팔레트 참조
|
|
8
|
+
4곳을 역할 토큰으로 옮겼다(`--u-blue-600` → `--u-primary-color`, `--u-blue-200` →
|
|
9
|
+
`--u-primary-color-weakest`). **두 테마의 계산색은 그대로다** — 브라우저에서
|
|
10
|
+
변경 전후 계산값 스냅샷을 대조해 확인했다.
|
|
11
|
+
|
|
12
|
+
제거한 다크 블록의 24개 선언은 전부 base 규칙과 **같은 토큰**을 가리키고 있었다.
|
|
13
|
+
역할 토큰(`--u-bg-color`, `--u-border-color`, `--u-txt-color-weak` …)은 두 테마에서
|
|
14
|
+
서로 다른 팔레트 단에 매핑돼 있어(예: `--u-bg-color` = neutral-0 라이트 /
|
|
15
|
+
neutral-100 다크) 테마 보정이 이미 토큰 층에 들어 있다. 따라서 그 블록은 토큰 시트가
|
|
16
|
+
로드된 환경에서 계산값을 하나도 바꾸지 못했다.
|
|
17
|
+
|
|
18
|
+
- **USimpleSheet 의 다크 블록을 24개 선언 → 6개로 줄였다.** 지운 것은 base 와 같은 토큰을
|
|
19
|
+
가리켜 계산값을 바꾸지 못하던 규칙들이다. **남긴 6개는 죽은 규칙이 아니다** — 유채색
|
|
20
|
+
표면(계산 셀·선택된 열 헤더·강조된 드롭다운 항목)이며, 역할 층에 유채색 표면 토큰이
|
|
21
|
+
없어서 손으로 메운 보정이다. 비활성화하면 실제로 색이 달라짐을 확인했다.
|
|
22
|
+
- **USimpleSheet 그림자 2곳이 `--u-shadow-color-*` 를 읽는다.** 종전에는 라이트/다크가
|
|
23
|
+
각각 하드코딩된 `rgba(0,0,0,0.15)` / `0.5` 였다. 역할 토큰은 테마별로 불투명도가
|
|
24
|
+
튜닝돼 있다(0.16/0.45, 0.12/0.35) — 다크에서 그림자가 아주 조금 옅어진다.
|
|
25
|
+
- **USimpleSheet 읽기전용 셀이 `--u-bg-color-disabled` 를 읽는다.** 이 토큰의 테마별
|
|
26
|
+
매핑(neutral-50 라이트 / neutral-100 다크)이 종전 다크 블록이 손으로 쓴 단과 **정확히
|
|
27
|
+
같다** — 그래서 해당 다크 규칙이 불필요해졌다. 계산색은 두 테마 모두 그대로다.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **UI 문구를 프로퍼티로 열었다** — URichTable 의 `loadingMessage`·`filterPlaceholder`·
|
|
32
|
+
`filterAllLabel`·`addRowLabel`·`pageInfoFormatter`, USimpleSheet 의 `noMatchMessage`.
|
|
33
|
+
종전에는 `emptyMessage` 하나만 열려 있고 나머지는 한국어로 하드코딩돼 있었다.
|
|
34
|
+
`pageInfoFormatter` 만 함수인 것은 **언어마다 어순이 달라** 템플릿 문자열로는 열 수
|
|
35
|
+
없기 때문이다.
|
|
36
|
+
|
|
37
|
+
⚠로케일 레지스트리를 도입하지는 않았다 — 이 패키지에 없는 레이어를 새로 들이는
|
|
38
|
+
일이고, 현재 수요가 그것을 요구할 만큼 모이지 않았다. 기존 `emptyMessage` 어법을
|
|
39
|
+
따르는 순수 가산이다.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- **업스트림 토큰 채택 (`@iyulab/components` 1.15.0).** 이 릴리스에서 소비 측 우회 둘이
|
|
44
|
+
사라졌다:
|
|
45
|
+
- **보조 텍스트 AA 우회 해제** — URichTable 4곳이 역할 토큰을 피해 고정값을 쓰고
|
|
46
|
+
있었다(`--u-txt-color-weak` 가 라이트에서 2.68 로 AA 미달이었기 때문). 업스트림이
|
|
47
|
+
neutral-600(4.61)으로 고쳐져 **역할 토큰으로 되돌렸다.** 그 넷을 위해 남겨 둔
|
|
48
|
+
다크 규칙도 함께 사라졌다 — 이제 이 색은 테마 오버라이드를 따른다.
|
|
49
|
+
- **유채색 표면 토큰 채택** — `--u-primary-bg-color`·`--u-success-bg-color`·
|
|
50
|
+
`--u-danger-bg-color` 로 옮겼다. 이 토큰들은 두 테마가 **같은 세기**가 되도록
|
|
51
|
+
짝지어져 있어(라이트 shade-0 ↔ 다크 shade-100), 손으로 쓰던 다크 보정 2개가
|
|
52
|
+
불필요해졌다. **다크에서 선택된 행·계산 셀 틴트가 제 세기로 보인다** —
|
|
53
|
+
종전에는 팔레트 유채색이 다크에서 짓눌려 절반 이하로 옅었다.
|
|
54
|
+
|
|
55
|
+
⇒ 다크 전용 규칙이 **8 → 3**, 팔레트 직접 참조가 **40 → 31** 로 줄었다.
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- **URichTable 템플릿에 인라인으로 박혀 있던 색 3곳**(행 메뉴 `⋯`, 행 오류 줄, 새 행
|
|
60
|
+
표시)을 클래스 + 역할 토큰으로 옮겼다. `style="…#94a3b8"` 형태라 **스타일 시트만
|
|
61
|
+
훑는 정리에는 보이지 않는 자리**였다. 색이 조금 바뀐다 — 행 오류 줄 배경
|
|
62
|
+
`#FEF2F2` → `#FFEBEE`, 행 메뉴 `#94A3B8` → `#9E9E9E`, 새 행 표시 `#86EFAC` →
|
|
63
|
+
`#A5D6A7`. (이 셋은 계산색 스냅샷이 덮지 못한다 — 렌더 조건이 내부 상태다.)
|
|
64
|
+
|
|
65
|
+
- ★**URichTable 에서 `_id` 없는 행을 넘기면 한 행 선택이 전체 선택으로 번지던 문제.**
|
|
66
|
+
선택·확장·행 오류 상태가 전부 `_id` 로 추적되는데 그 값을 **어디서도 부여하지 않아**,
|
|
67
|
+
소비자가 넣지 않으면 모든 행이 `undefined` 로 같은 키를 갖게 됐다. 이제 `_id` 가 없으면
|
|
68
|
+
**위치로 식별하고 개발 경고**를 낸다. 위치 기반 식별은 데이터가 재정렬·재페이징되면
|
|
69
|
+
선택이 다른 행으로 옮겨가므로 안전망일 뿐이다 — 정렬·필터·페이지가 소비자 책임인
|
|
70
|
+
컴포넌트이니 `_id` 를 주는 것이 옳다.
|
|
71
|
+
|
|
72
|
+
### Documentation
|
|
73
|
+
|
|
74
|
+
- **`docs/URichTable.md` 신설.** 종전에는 README 가 *"문서 작성 예정"* 이라며 `types.ts`
|
|
75
|
+
를 가리키고 있었다. 타입만으로는 드러나지 않는 두 계약을 먼저 적었다 — ⑴ `data` 는
|
|
76
|
+
그대로 렌더되며 **정렬·필터·페이지는 소비자 책임**이다(헤더 클릭은 이벤트만 낸다),
|
|
77
|
+
⑵ 각 행에 `_id` 가 필요하다. 속성·이벤트 9종·키보드/클립보드 표 포함.
|
|
78
|
+
- **README 의 Theming 절을 실제 동작에 맞게 고쳤다** — 토큰 시트가 필요하다는 것,
|
|
79
|
+
없을 때의 폴백 규약, 브랜드 색 한 줄 변경, 그리고 알려진 제약 둘(AA 미달, Chromium
|
|
80
|
+
한정 규칙)을 명시했다.
|
|
81
|
+
|
|
82
|
+
- **`--u-blue-50` 은 토큰 시트에 정의가 없다.** USimpleSheet 4곳(계산 셀·선택 셀·강조된
|
|
83
|
+
드롭다운 항목)이 이 이름을 참조해 **두 테마 모두에서** 항상 리터럴 폴백으로 떨어지고
|
|
84
|
+
있었다 — 즉 토큰 층과 무관하게 고정색이었다. 팔레트의 틴트 끝인 `--u-blue-0` 로
|
|
85
|
+
고쳤다. **라이트에서 색이 바뀐다**: `#EFF6FF` → `#E3F2FD`. 다크는 종전과 동일하다.
|
|
86
|
+
|
|
87
|
+
- ★**URichTable 이 디자인 토큰을 읽기 시작한다 — 라이트 재도색.** 이 컴포넌트는 base
|
|
88
|
+
레이어가 토큰을 **하나도** 쓰지 않고 고정 hex 를 박아 두었다. 즉 라이트에서 테마·브랜드
|
|
89
|
+
설정에 전혀 반응하지 않았고, 다크는 그 위에 얹은 별도 재구현(57개 선언)이었다.
|
|
90
|
+
base 를 역할 토큰으로 옮기면서 다크 블록은 **2개 규칙**만 남았다.
|
|
91
|
+
|
|
92
|
+
**라이트에서 색이 바뀐다** — 주요 항목:
|
|
93
|
+
|
|
94
|
+
| 자리 | 전 | 후 |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| 테두리 | `#E2E8F0` | `#E0E0E0` (`--u-border-color`) |
|
|
97
|
+
| 입력 테두리 | `#D1D5DB` | `#E0E0E0` (`--u-input-border-color`) |
|
|
98
|
+
| 툴바·페이지네이션 배경 | `#F8FAFC` | `#FAFAFA` |
|
|
99
|
+
| 헤더 배경 | `#F1F5F9` | `#F5F5F5` |
|
|
100
|
+
| 주 버튼 | `#3B82F6` | `#1E88E5` (`--u-primary-color`) |
|
|
101
|
+
| 성공 버튼 | `#10B981` | `#43A047` (`--u-success-color`) |
|
|
102
|
+
| 선택된 행 | `#EFF6FF` | `#E3F2FD` |
|
|
103
|
+
| 편집·필터 행 | `#FEFCE8` | `#FFFDE7` |
|
|
104
|
+
| 오류 행 | `#FEF2F2` | `#FFEBEE` |
|
|
105
|
+
| 새 행 | `#F0FDF4` | `#E8F5E9` |
|
|
106
|
+
|
|
107
|
+
⇒ 이제 `:root { --u-primary-color: … }` 한 줄로 이 표의 버튼·강조색이 따라온다.
|
|
108
|
+
|
|
109
|
+
**다크에서는 성공 버튼만 바뀐다**(green-500 → `--u-success-color`). 나머지가 종전
|
|
110
|
+
값 그대로임을 계산색 스냅샷으로 확인했다 — 두 테마 × 14개 선택자(상태 틴트 포함:
|
|
111
|
+
선택된 행·필터 줄·새 행). 스냅샷이 덮지 못한 자리는 `tbody tr.error`(내부 상태 필요)
|
|
112
|
+
와 `.dropdown-item.highlighted` 두 곳이다.
|
|
113
|
+
|
|
114
|
+
⚠**`:host` 가 이제 글자색을 정한다.** 종전에는 base 에 `color` 선언이 없어 페이지에서
|
|
115
|
+
**상속**받았고, 다크 블록만 그것을 덮었다. 이제 base 가 `--u-txt-color` 를 읽는다.
|
|
116
|
+
토큰 시트를 쓰는 환경에서는 개선이지만, 시트 없이 페이지 글자색을 직접 지정하던
|
|
117
|
+
소비자는 그 상속을 잃는다 — 필요하면 `--u-txt-color` 를 지정하세요.
|
|
118
|
+
|
|
119
|
+
⚠**Firefox/Safari 에서 다크가 동작하기 시작한다.** 종전에는 다크가 전부
|
|
120
|
+
`:host-context()` 안에 있었고 그 선택자는 Chromium 전용이라, 다른 브라우저에서는
|
|
121
|
+
**다크 모드가 아예 적용되지 않았다.** 이제 색의 대부분이 토큰 층에서 테마를 따르므로
|
|
122
|
+
전 브라우저에서 동작한다. 남은 2개 규칙(표면 높이·보조 텍스트)만 Chromium 한정으로
|
|
123
|
+
남는다.
|
|
124
|
+
|
|
125
|
+
### Removed
|
|
126
|
+
|
|
127
|
+
- **UDataView 의 `theme` 속성이 더 이상 색을 바꾸지 않는다.**
|
|
128
|
+
토큰 시트를 로드한 환경에서는 원래부터 효과가 없었다(위 참조). 시트를 로드하지 않는
|
|
129
|
+
환경에서는 요소에 `theme="dark"` 를 걸면 다크 고정값이 적용됐으나, 이제는 적용되지
|
|
130
|
+
않는다 — 토큰 미공급 시 폴백은 라이트 기준 고정값이라는 규약을 따른다.
|
|
131
|
+
**다크가 필요하면 토큰 시트를 로드할 것**(`@iyulab/components/styles/tokens.css`).
|
|
132
|
+
문서 단위 다크(`<html theme="dark">`)는 종전과 동일하게 동작한다.
|
|
133
|
+
|
|
134
|
+
### Added
|
|
135
|
+
|
|
136
|
+
- 브라우저 테스트 프로젝트(playwright chromium). 계산색을 실제로 측정한다 —
|
|
137
|
+
기존 유닛 테스트는 스타일 **문자열**에서 셀렉터를 세므로, 규칙이 적혀 있음은 보여도
|
|
138
|
+
그 규칙이 무언가를 바꾸는지는 말하지 못한다. 위 24개 선언이 정확히 그 사각에 있었다.
|
|
139
|
+
- `data-view-colors.browser.test.ts` — 두 테마 계산색 스냅샷 회귀망
|
|
140
|
+
- `theme-resolution.browser.test.ts` — 역할 토큰의 테마별 매핑, 팔레트 틴트 비대칭
|
|
141
|
+
|
|
142
|
+
### Known issues
|
|
143
|
+
|
|
144
|
+
- **유채색에는 테마 보정 층이 없다.** 역할 토큰의 중립 계열 17종은 두 테마에서 다른
|
|
145
|
+
팔레트 단에 매핑되지만, `--u-primary/success/danger/warning-*` 는 양 테마가 같은 단을
|
|
146
|
+
가리킨다. 그런데 팔레트의 틴트 세기는 계열마다 비대칭이다 — 바탕 대비 델타 실측:
|
|
147
|
+
중립은 다크가 라이트의 1.34~1.62배, 청색은 0.38~0.54배, 적색은 0.25~0.58배,
|
|
148
|
+
황색은 4.2~6.1배. 그래서 유채색 표면을 쓰는 컴포넌트는 다크 보정을 손으로 써야 한다.
|
|
149
|
+
- **"주색 위의 글자" 역할 토큰이 없다.** 활성 뷰 버튼은 다크에서 대비 3.45 이며
|
|
150
|
+
흰 글자였다면 6.09 다. 팔레트를 직접 바꾸면 게시된 시각이 움직이므로 보류했다.
|
|
151
|
+
- **UI 문자열이 한국어로 하드코딩돼 있다** — `URichTable` 의 `로딩 중...`·`필터...`·
|
|
152
|
+
`전체`·`전체 N건 중 X-Y 표시`, `USimpleSheet` 의 `일치하는 항목 없음`. `emptyMessage`
|
|
153
|
+
만 프로퍼티로 열려 있고 나머지는 소비자가 바꿀 수 없다. 이 패키지에는 로케일 레이어가
|
|
154
|
+
없어 단순 프로퍼티 추가로 갈지 로케일 레지스트리를 도입할지가 설계 결정이다.
|
|
155
|
+
- ★**`--u-txt-color-weak` 의 라이트 매핑이 WCAG AA 에 미달한다** — neutral-500(#9E9E9E)
|
|
156
|
+
= 흰 배경 대비 **2.68**(AA 기준 4.5). 다크는 neutral-700 = **5.43** 으로 정상이므로
|
|
157
|
+
**라이트 한쪽만의 결함**이다. 그래서 URichTable 의 보조 텍스트 4곳은 역할 토큰으로
|
|
158
|
+
옮기지 못했다(옮기면 4.76 → 2.68 로 후퇴). 업스트림에서 라이트 매핑을
|
|
159
|
+
**neutral-600(4.61)** 으로 한 단 내리면 해소된다.
|
|
160
|
+
|
|
3
161
|
## [0.7.1] - 2026-08-01
|
|
4
162
|
|
|
5
163
|
### Documentation
|
package/README.md
CHANGED
|
@@ -124,12 +124,12 @@ const sheet = document.querySelector('u-simple-sheet'); // USimpleSheet | null
|
|
|
124
124
|
|
|
125
125
|
- [USimpleSheet](./docs/USimpleSheet.md)
|
|
126
126
|
- [UDataView](./docs/UDataView.md)
|
|
127
|
-
- URichTable
|
|
127
|
+
- [URichTable](./docs/URichTable.md)
|
|
128
128
|
- [UDataGrid 마이그레이션 가이드](./docs/migrating-from-datagrid.md)
|
|
129
129
|
|
|
130
130
|
## Theming
|
|
131
131
|
|
|
132
|
-
`@iyulab/components`의
|
|
132
|
+
`@iyulab/components`의 디자인 토큰을 읽습니다. **토큰 시트가 문서에 있어야 테마가 동작합니다.**
|
|
133
133
|
|
|
134
134
|
```typescript
|
|
135
135
|
import { Theme } from '@iyulab/components';
|
|
@@ -138,6 +138,40 @@ Theme.init({ default: 'system' });
|
|
|
138
138
|
Theme.set('dark'); // 'light' | 'dark' | 'system'
|
|
139
139
|
```
|
|
140
140
|
|
|
141
|
+
`Theme.init()`을 쓰지 않는다면 정적 진입점을 대신 임포트하세요:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import '@iyulab/components/styles/tokens.css';
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 토큰 시트가 없으면
|
|
148
|
+
|
|
149
|
+
컴포넌트는 렌더되지만 **라이트 기준 고정값**으로 그려지며, 다크 테마와 테마 변수
|
|
150
|
+
오버라이드가 적용되지 않습니다. 이는 의도된 폴백 규약입니다.
|
|
151
|
+
|
|
152
|
+
### 브랜드 색 바꾸기
|
|
153
|
+
|
|
154
|
+
색은 역할 토큰을 통해 읽으므로, 주색 한 줄로 버튼·선택 강조·포커스가 함께 따라옵니다:
|
|
155
|
+
|
|
156
|
+
```css
|
|
157
|
+
:root { --u-primary-color: #7B1FA2; }
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 알려진 제약
|
|
161
|
+
|
|
162
|
+
- **표면 높이를 뜻하는 토큰이 없습니다** — 배경 역할 토큰은 상호작용 상태 이름
|
|
163
|
+
(`-hover`/`-active`/`-disabled`)뿐이라 "바탕보다 한 단 올라온 면"을 표현할 수 없습니다.
|
|
164
|
+
라이트에서는 팔레트 음영이 흰 바탕 위에서 충분히 읽히지만 다크에서는 거의 검정이라,
|
|
165
|
+
툴바·표 헤더 등 몇 자리는 `:host-context()`로 보정합니다. **이 선택자는
|
|
166
|
+
Firefox/Safari에서 동작하지 않습니다** — 해당 브라우저에서 그 면들이 배경에 묻힙니다.
|
|
167
|
+
그 밖의 색은 전 브라우저에서 테마를 따릅니다.
|
|
168
|
+
- **경고(노랑) 표면에는 역할 토큰이 없습니다** — 라이트 황색 틴트가 거의 보이지 않아
|
|
169
|
+
두 테마의 세기를 맞출 단 짝이 없습니다(업스트림 미해결). 필터 줄·편집 중 행은
|
|
170
|
+
팔레트를 직접 읽습니다.
|
|
171
|
+
|
|
172
|
+
> `--u-txt-color-weak`의 WCAG AA 미달(2.68)은 `@iyulab/components` 1.15.0에서
|
|
173
|
+
> 해소됐습니다(4.61). 이 패키지가 쓰던 우회도 함께 제거됐습니다.
|
|
174
|
+
|
|
141
175
|
## License
|
|
142
176
|
|
|
143
177
|
MIT
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { css
|
|
2
|
-
|
|
3
|
-
var baseStyles = css`
|
|
1
|
+
import { css } from "lit";
|
|
2
|
+
var styles = [css`
|
|
4
3
|
:host {
|
|
5
4
|
display: block;
|
|
6
5
|
width: 100%;
|
|
@@ -30,7 +29,12 @@ var baseStyles = css`
|
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
.view-toggles u-button[active] {
|
|
33
|
-
background: var(--u-
|
|
32
|
+
background: var(--u-primary-color, #1E88E5);
|
|
33
|
+
/* 예외 1 — "주색 위의 글자" 역할 토큰이 없다.
|
|
34
|
+
역할 층의 유채색은 전경 5단(--u-primary-color-*)뿐이고, 그 위에 얹는 글자색을
|
|
35
|
+
가리키는 토큰은 없다. 실측: 라이트 #FFFFFF/#1E88E5 = 3.68, 다크 #000000/#2A659D
|
|
36
|
+
= 3.45 — 다크는 흰 글자였다면 6.09 였다. 즉 현재 값이 최선이 아니지만, 팔레트를
|
|
37
|
+
직접 바꾸면 게시된 시각이 움직이므로 토큰 추가 결정까지 현행을 유지한다. */
|
|
34
38
|
color: var(--u-neutral-0, #fff);
|
|
35
39
|
}
|
|
36
40
|
|
|
@@ -65,15 +69,15 @@ var baseStyles = css`
|
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
.card:hover {
|
|
68
|
-
border-color: var(--u-
|
|
72
|
+
border-color: var(--u-primary-color, #1E88E5);
|
|
69
73
|
box-shadow: 0 4px 12px var(--u-shadow-color-weak, rgba(0, 0, 0, 0.08));
|
|
70
74
|
transform: translateY(-2px);
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
.card.selected {
|
|
74
|
-
border-color: var(--u-
|
|
78
|
+
border-color: var(--u-primary-color, #1E88E5);
|
|
75
79
|
background: var(--u-bg-color-active, #EEEEEE);
|
|
76
|
-
box-shadow: 0 0 0 3px var(--u-
|
|
80
|
+
box-shadow: 0 0 0 3px var(--u-primary-color-weakest, #90CAF9);
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
.card-content {
|
|
@@ -127,6 +131,11 @@ var baseStyles = css`
|
|
|
127
131
|
}
|
|
128
132
|
|
|
129
133
|
thead {
|
|
134
|
+
/* 예외 2 — 표면 높이 축이 없다.
|
|
135
|
+
배경 역할 토큰은 상호작용 상태 이름(-hover/-active/-disabled)뿐이라,
|
|
136
|
+
"바탕보다 한 단 올라온 면"을 뜻하는 토큰이 없다. 헤더에 -hover 를 쓰면
|
|
137
|
+
마우스 상태를 뜻하게 되므로 팔레트를 직접 읽는다. neutral-50 은 두 테마에서
|
|
138
|
+
각각 #FAFAFA/#0A0A0A 로 반전되므로 다크 보정은 필요 없다. */
|
|
130
139
|
background: var(--u-neutral-50, #f8fafc);
|
|
131
140
|
position: sticky;
|
|
132
141
|
top: 0;
|
|
@@ -175,88 +184,6 @@ var baseStyles = css`
|
|
|
175
184
|
color: var(--u-txt-color-weak, #64748b);
|
|
176
185
|
font-size: 1rem;
|
|
177
186
|
}
|
|
178
|
-
|
|
179
|
-
var DARK_PREFIXES = [
|
|
180
|
-
":host([theme=\"dark\"])",
|
|
181
|
-
":host-context([theme=\"dark\"])",
|
|
182
|
-
":host-context([data-theme=\"dark\"])"
|
|
183
|
-
];
|
|
184
|
-
var darkRules = (host) => `
|
|
185
|
-
${host} .toolbar {
|
|
186
|
-
background: var(--u-bg-color, #121212);
|
|
187
|
-
border-color: var(--u-border-color, #3D3D3D);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
${host} .view-toggles u-button[active] {
|
|
191
|
-
background: var(--u-blue-600, #87B8F5);
|
|
192
|
-
color: var(--u-neutral-0, #000);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
${host} .info {
|
|
196
|
-
color: var(--u-txt-color-weak, #8A8A8A);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
${host} .card {
|
|
200
|
-
background: var(--u-bg-color, #121212);
|
|
201
|
-
border-color: var(--u-border-color, #3D3D3D);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
${host} .card:hover {
|
|
205
|
-
border-color: var(--u-blue-600, #87B8F5);
|
|
206
|
-
box-shadow: 0 4px 12px var(--u-shadow-color-weak, rgba(0, 0, 0, 0.25));
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
${host} .card.selected {
|
|
210
|
-
border-color: var(--u-blue-600, #87B8F5);
|
|
211
|
-
background: var(--u-bg-color-active, #3D3D3D);
|
|
212
|
-
box-shadow: 0 0 0 3px var(--u-blue-200, #2B4F7E);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
${host} .card-field .label {
|
|
216
|
-
color: var(--u-txt-color-weak, #8A8A8A);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
${host} .card-field .value {
|
|
220
|
-
color: var(--u-txt-color, #D4D4D4);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
${host} .table-wrapper {
|
|
224
|
-
border-color: var(--u-border-color, #3D3D3D);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
${host} table {
|
|
228
|
-
background: var(--u-bg-color, #121212);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
${host} thead {
|
|
232
|
-
background: var(--u-neutral-50, #0A0A0A);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
${host} th {
|
|
236
|
-
color: var(--u-txt-color-weak, #8A8A8A);
|
|
237
|
-
border-bottom-color: var(--u-border-color, #3D3D3D);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
${host} tbody tr:hover {
|
|
241
|
-
background: var(--u-bg-color-hover, #2A2A2A);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
${host} tbody tr.selected {
|
|
245
|
-
background: var(--u-bg-color-active, #3D3D3D);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
${host} tbody tr:not(:last-child) {
|
|
249
|
-
border-bottom-color: var(--u-border-color-weak, #2A2A2A);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
${host} td {
|
|
253
|
-
color: var(--u-txt-color, #D4D4D4);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
${host} .empty {
|
|
257
|
-
color: var(--u-txt-color-weak, #8A8A8A);
|
|
258
|
-
}
|
|
259
|
-
`;
|
|
260
|
-
var styles = [baseStyles, unsafeCSS(DARK_PREFIXES.map(darkRules).join("\n"))];
|
|
187
|
+
`];
|
|
261
188
|
//#endregion
|
|
262
189
|
export { styles };
|
|
@@ -48,6 +48,8 @@ export declare class USimpleSheet extends UElement {
|
|
|
48
48
|
cols: number;
|
|
49
49
|
/** 읽기 전용 모드 */
|
|
50
50
|
readonly: boolean;
|
|
51
|
+
/** 드롭다운에 일치 항목이 없을 때 문구 */
|
|
52
|
+
noMatchMessage: string;
|
|
51
53
|
/**
|
|
52
54
|
* 명시 테마. 'dark'이면 다크 스타일을 적용합니다.
|
|
53
55
|
* 미설정 시 조상 컨텍스트의 theme/data-theme 속성에 따릅니다 (:host-context, Chromium 한정).
|
|
@@ -25,6 +25,7 @@ var USimpleSheet = class USimpleSheet extends UElement {
|
|
|
25
25
|
this.rows = 20;
|
|
26
26
|
this.cols = 10;
|
|
27
27
|
this.readonly = false;
|
|
28
|
+
this.noMatchMessage = "일치하는 항목 없음";
|
|
28
29
|
this._sel = null;
|
|
29
30
|
this._editing = null;
|
|
30
31
|
this._editVal = "";
|
|
@@ -611,7 +612,7 @@ var USimpleSheet = class USimpleSheet extends UElement {
|
|
|
611
612
|
</div>
|
|
612
613
|
` : noMatch && isStrict ? html`
|
|
613
614
|
<div class="cell-dropdown">
|
|
614
|
-
<div class="dropdown-empty"
|
|
615
|
+
<div class="dropdown-empty">${this.noMatchMessage}</div>
|
|
615
616
|
</div>
|
|
616
617
|
` : ""}
|
|
617
618
|
` : this._formatValue(value, c, r)}
|
|
@@ -924,6 +925,7 @@ __decorate([property({ type: Array }), __decorateMetadata("design:type", Array)]
|
|
|
924
925
|
__decorate([property({ type: Number }), __decorateMetadata("design:type", Object)], USimpleSheet.prototype, "rows", void 0);
|
|
925
926
|
__decorate([property({ type: Number }), __decorateMetadata("design:type", Object)], USimpleSheet.prototype, "cols", void 0);
|
|
926
927
|
__decorate([property({ type: Boolean }), __decorateMetadata("design:type", Object)], USimpleSheet.prototype, "readonly", void 0);
|
|
928
|
+
__decorate([property({ type: String }), __decorateMetadata("design:type", Object)], USimpleSheet.prototype, "noMatchMessage", void 0);
|
|
927
929
|
__decorate([property({
|
|
928
930
|
type: String,
|
|
929
931
|
reflect: true
|
|
@@ -125,6 +125,9 @@ var baseStyles = css`
|
|
|
125
125
|
padding: 0 6px;
|
|
126
126
|
font-size: 11px;
|
|
127
127
|
color: var(--u-txt-color-weak, #64748b);
|
|
128
|
+
/* 예외 — 표면 높이 축이 없다. 행번호 여백은 "비활성"이 아니라 시트 바탕보다
|
|
129
|
+
한 단 내려간 면이다. --u-bg-color-disabled 를 쓰면 다크에서 바탕(neutral-100)과
|
|
130
|
+
같은 값이 되어 여백 구분이 사라진다 — 스냅샷이 그것을 잡았다. */
|
|
128
131
|
background: var(--u-neutral-50, #f8fafc);
|
|
129
132
|
border-right: 1px solid var(--u-border-color, #e2e8f0);
|
|
130
133
|
border-bottom: 1px solid var(--u-border-color-weak, #f1f5f9);
|
|
@@ -159,7 +162,7 @@ var baseStyles = css`
|
|
|
159
162
|
}
|
|
160
163
|
|
|
161
164
|
.cell.selected {
|
|
162
|
-
background: var(--u-
|
|
165
|
+
background: var(--u-primary-bg-color, #E3F2FD);
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
/* The anchor cell (top-left of selection) */
|
|
@@ -194,7 +197,7 @@ var baseStyles = css`
|
|
|
194
197
|
background: var(--u-bg-color, #fff);
|
|
195
198
|
box-sizing: border-box;
|
|
196
199
|
z-index: 10;
|
|
197
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.
|
|
200
|
+
box-shadow: 0 2px 8px var(--u-shadow-color-strong, rgba(0, 0, 0, 0.16));
|
|
198
201
|
}
|
|
199
202
|
|
|
200
203
|
/* Dropdown overlay */
|
|
@@ -210,7 +213,7 @@ var baseStyles = css`
|
|
|
210
213
|
border: 1px solid var(--u-border-color, #e2e8f0);
|
|
211
214
|
border-top: none;
|
|
212
215
|
border-radius: 0 0 4px 4px;
|
|
213
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
|
216
|
+
box-shadow: 0 4px 12px var(--u-shadow-color-normal, rgba(0, 0, 0, 0.12));
|
|
214
217
|
z-index: 20;
|
|
215
218
|
}
|
|
216
219
|
|
|
@@ -229,7 +232,7 @@ var baseStyles = css`
|
|
|
229
232
|
}
|
|
230
233
|
|
|
231
234
|
.dropdown-item.highlighted {
|
|
232
|
-
background: var(--u-
|
|
235
|
+
background: var(--u-primary-bg-color, #E3F2FD);
|
|
233
236
|
color: var(--u-blue-800, #1e40af);
|
|
234
237
|
}
|
|
235
238
|
|
|
@@ -247,7 +250,7 @@ var baseStyles = css`
|
|
|
247
250
|
|
|
248
251
|
/* Readonly column/cell */
|
|
249
252
|
.cell.cell-readonly {
|
|
250
|
-
background: var(--u-
|
|
253
|
+
background: var(--u-bg-color-disabled, #FAFAFA);
|
|
251
254
|
color: var(--u-txt-color-weak, #64748b);
|
|
252
255
|
cursor: default;
|
|
253
256
|
}
|
|
@@ -257,12 +260,12 @@ var baseStyles = css`
|
|
|
257
260
|
}
|
|
258
261
|
|
|
259
262
|
.cell.cell-readonly.anchor {
|
|
260
|
-
background: var(--u-
|
|
263
|
+
background: var(--u-bg-color-disabled, #FAFAFA);
|
|
261
264
|
}
|
|
262
265
|
|
|
263
266
|
/* Computed column/cell */
|
|
264
267
|
.cell.cell-computed {
|
|
265
|
-
background: var(--u-
|
|
268
|
+
background: var(--u-primary-bg-color, #E3F2FD);
|
|
266
269
|
color: var(--u-txt-color, #0f172a);
|
|
267
270
|
font-style: italic;
|
|
268
271
|
cursor: default;
|
|
@@ -273,7 +276,7 @@ var baseStyles = css`
|
|
|
273
276
|
}
|
|
274
277
|
|
|
275
278
|
.cell.cell-computed.anchor {
|
|
276
|
-
background: var(--u-
|
|
279
|
+
background: var(--u-primary-bg-color, #E3F2FD);
|
|
277
280
|
}
|
|
278
281
|
|
|
279
282
|
/* Readonly mode (whole sheet) */
|
|
@@ -288,71 +291,14 @@ var DARK_PREFIXES = [
|
|
|
288
291
|
":host-context([data-theme=\"dark\"])"
|
|
289
292
|
];
|
|
290
293
|
var darkRules = (host) => `
|
|
291
|
-
${host} .sheet-container {
|
|
292
|
-
background: var(--u-bg-color, #121212);
|
|
293
|
-
border-color: var(--u-border-color, #3D3D3D);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
${host} .sheet-container:focus-within {
|
|
297
|
-
border-color: var(--u-blue-500, #6ba3e3);
|
|
298
|
-
box-shadow: 0 0 0 2px var(--u-blue-100, #1e3a5f);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
${host} .corner {
|
|
302
|
-
background: var(--u-neutral-100, #121212);
|
|
303
|
-
border-color: var(--u-border-color, #3D3D3D);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
${host} .corner:hover {
|
|
307
|
-
background: var(--u-neutral-200, #1E1E1E);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
${host} .col-header {
|
|
311
|
-
background: var(--u-neutral-100, #121212);
|
|
312
|
-
color: var(--u-txt-color-weak, #8A8A8A);
|
|
313
|
-
border-color: var(--u-border-color, #3D3D3D);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
294
|
${host} .col-header.col-selected {
|
|
317
295
|
background: var(--u-blue-100, #1e3a5f);
|
|
318
|
-
color: var(--u-blue-800, #c2deff);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
${host} .row-num {
|
|
322
|
-
background: var(--u-neutral-50, #0A0A0A);
|
|
323
|
-
color: var(--u-txt-color-weak, #8A8A8A);
|
|
324
|
-
border-right-color: var(--u-border-color, #3D3D3D);
|
|
325
|
-
border-bottom-color: var(--u-border-color-weak, #2A2A2A);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
${host} .row-num.row-selected {
|
|
329
|
-
background: var(--u-blue-100, #1e3a5f);
|
|
330
|
-
color: var(--u-blue-800, #c2deff);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
${host} .cell {
|
|
334
|
-
color: var(--u-txt-color, #D4D4D4);
|
|
335
|
-
border-right-color: var(--u-border-color-weak, #2A2A2A);
|
|
336
|
-
border-bottom-color: var(--u-border-color-weak, #2A2A2A);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
${host} .cell.cell-readonly {
|
|
340
|
-
background: var(--u-neutral-100, #1E1E1E);
|
|
341
|
-
color: var(--u-txt-color-weak, #8A8A8A);
|
|
342
296
|
}
|
|
343
297
|
|
|
344
298
|
${host} .cell.cell-readonly.selected {
|
|
345
299
|
background: var(--u-neutral-200, #2A2A2A);
|
|
346
300
|
}
|
|
347
301
|
|
|
348
|
-
${host} .cell.cell-readonly.anchor {
|
|
349
|
-
background: var(--u-neutral-100, #1E1E1E);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
${host} .cell.cell-computed {
|
|
353
|
-
background: var(--u-blue-100, #1e3a5f);
|
|
354
|
-
color: var(--u-txt-color, #D4D4D4);
|
|
355
|
-
}
|
|
356
302
|
|
|
357
303
|
${host} .cell.cell-computed.selected {
|
|
358
304
|
background: var(--u-blue-200, #2a4a6f);
|
|
@@ -362,48 +308,6 @@ var darkRules = (host) => `
|
|
|
362
308
|
background: var(--u-blue-100, #1e3a5f);
|
|
363
309
|
}
|
|
364
310
|
|
|
365
|
-
${host} .cell.selected {
|
|
366
|
-
background: var(--u-blue-0, #0a1e3d);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
${host} .cell.anchor {
|
|
370
|
-
background: var(--u-bg-color, #121212);
|
|
371
|
-
outline-color: var(--u-blue-500, #6ba3e3);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
${host} .resize-handle:hover::after {
|
|
375
|
-
background: var(--u-blue-500, #6ba3e3);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
${host} .cell-input {
|
|
379
|
-
background: var(--u-bg-color, #121212);
|
|
380
|
-
color: var(--u-txt-color, #D4D4D4);
|
|
381
|
-
outline-color: var(--u-blue-500, #6ba3e3);
|
|
382
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
${host} .cell-dropdown {
|
|
386
|
-
background: var(--u-bg-color, #121212);
|
|
387
|
-
border-color: var(--u-border-color, #3D3D3D);
|
|
388
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
${host} .dropdown-item {
|
|
392
|
-
color: var(--u-txt-color, #D4D4D4);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
${host} .dropdown-item:hover {
|
|
396
|
-
background: var(--u-neutral-100, #1E1E1E);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
${host} .dropdown-item.highlighted {
|
|
400
|
-
background: var(--u-blue-100, #1e3a5f);
|
|
401
|
-
color: var(--u-blue-800, #c2deff);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
${host} .dropdown-empty {
|
|
405
|
-
color: var(--u-txt-color-weak, #8A8A8A);
|
|
406
|
-
}
|
|
407
311
|
`;
|
|
408
312
|
var styles = [baseStyles, unsafeCSS(DARK_PREFIXES.map(darkRules).join("\n"))];
|
|
409
313
|
//#endregion
|
|
@@ -9,6 +9,19 @@ export declare class URichTable extends LitElement {
|
|
|
9
9
|
currentPage: number;
|
|
10
10
|
loading: boolean;
|
|
11
11
|
emptyMessage: string;
|
|
12
|
+
/** 로딩 표시 문구 */
|
|
13
|
+
loadingMessage: string;
|
|
14
|
+
/** 필터 입력 placeholder */
|
|
15
|
+
filterPlaceholder: string;
|
|
16
|
+
/** 필터 select 의 "전체" 항목 문구 */
|
|
17
|
+
filterAllLabel: string;
|
|
18
|
+
/** 새 행 추가 버튼 문구 */
|
|
19
|
+
addRowLabel: string;
|
|
20
|
+
/**
|
|
21
|
+
* 페이지 정보 문구. (전체, 시작, 끝) 을 받아 문자열을 만든다.
|
|
22
|
+
* 언어마다 어순이 달라 템플릿 문자열이 아니라 함수로 연다.
|
|
23
|
+
*/
|
|
24
|
+
pageInfoFormatter: (total: number, start: number, end: number) => string;
|
|
12
25
|
selectable: boolean;
|
|
13
26
|
editable: boolean;
|
|
14
27
|
addable: boolean;
|
|
@@ -27,6 +40,18 @@ export declare class URichTable extends LitElement {
|
|
|
27
40
|
revertRow(_rowId: string): void;
|
|
28
41
|
setRowError(rowId: string, message: string): void;
|
|
29
42
|
clearRowError(rowId: string): void;
|
|
43
|
+
/**
|
|
44
|
+
* 행의 식별자. 선택·확장·행 오류 상태가 전부 이 값으로 추적된다.
|
|
45
|
+
*
|
|
46
|
+
* ⚠`_id` 는 이 컴포넌트가 **부여하지 않는다.** 소비자가 넣어 주지 않으면 모든 행의
|
|
47
|
+
* `_id` 가 `undefined` 가 되고, Set 은 그 하나만 담으므로 **한 행을 고르면 전부
|
|
48
|
+
* 골라진다.** 그래서 없을 때는 위치를 대신 쓴다 — 다만 위치 기반 식별은 데이터가
|
|
49
|
+
* 재정렬·재페이징되면 **선택이 다른 행으로 옮겨간다.** 정렬/필터/페이지가 소비자
|
|
50
|
+
* 책임인 컴포넌트이므로, 실제 사용에서는 `_id` 를 주는 것이 옳다.
|
|
51
|
+
*/
|
|
52
|
+
private _rowId;
|
|
53
|
+
private _warnedMissingRowId;
|
|
54
|
+
private _warnMissingRowId;
|
|
30
55
|
getSelectedRows(): Record<string, unknown>[];
|
|
31
56
|
render(): TemplateResult;
|
|
32
57
|
private _renderToolbar;
|
|
@@ -15,6 +15,11 @@ var URichTable = class URichTable extends LitElement {
|
|
|
15
15
|
this.currentPage = 1;
|
|
16
16
|
this.loading = false;
|
|
17
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} 표시`;
|
|
18
23
|
this.selectable = false;
|
|
19
24
|
this.editable = false;
|
|
20
25
|
this.addable = false;
|
|
@@ -29,6 +34,7 @@ var URichTable = class URichTable extends LitElement {
|
|
|
29
34
|
this.filters = {};
|
|
30
35
|
this.validationErrors = /* @__PURE__ */ new Map();
|
|
31
36
|
this.rowErrors = /* @__PURE__ */ new Map();
|
|
37
|
+
this._warnedMissingRowId = false;
|
|
32
38
|
this._lastSelectedIndex = -1;
|
|
33
39
|
this._onGlobalKeyDown = (e) => {
|
|
34
40
|
if (e.ctrlKey || e.metaKey) {
|
|
@@ -65,7 +71,8 @@ var URichTable = class URichTable extends LitElement {
|
|
|
65
71
|
}
|
|
66
72
|
if (e.key === " " && this.selectable) {
|
|
67
73
|
e.preventDefault();
|
|
68
|
-
const
|
|
74
|
+
const r = this.data[this.focusedCell.rowIndex];
|
|
75
|
+
const rowId = r ? this._rowId(r, this.focusedCell.rowIndex) : void 0;
|
|
69
76
|
if (rowId) this._onRowSelect(rowId);
|
|
70
77
|
}
|
|
71
78
|
if (e.key === "Delete" && this.selectedIds.size > 0) for (const row of this.getSelectedRows()) this.dispatchEvent(new CustomEvent("row-delete", {
|
|
@@ -88,8 +95,28 @@ var URichTable = class URichTable extends LitElement {
|
|
|
88
95
|
next.delete(rowId);
|
|
89
96
|
this.rowErrors = next;
|
|
90
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* 행의 식별자. 선택·확장·행 오류 상태가 전부 이 값으로 추적된다.
|
|
100
|
+
*
|
|
101
|
+
* ⚠`_id` 는 이 컴포넌트가 **부여하지 않는다.** 소비자가 넣어 주지 않으면 모든 행의
|
|
102
|
+
* `_id` 가 `undefined` 가 되고, Set 은 그 하나만 담으므로 **한 행을 고르면 전부
|
|
103
|
+
* 골라진다.** 그래서 없을 때는 위치를 대신 쓴다 — 다만 위치 기반 식별은 데이터가
|
|
104
|
+
* 재정렬·재페이징되면 **선택이 다른 행으로 옮겨간다.** 정렬/필터/페이지가 소비자
|
|
105
|
+
* 책임인 컴포넌트이므로, 실제 사용에서는 `_id` 를 주는 것이 옳다.
|
|
106
|
+
*/
|
|
107
|
+
_rowId(row, index) {
|
|
108
|
+
const id = row._id;
|
|
109
|
+
if (id !== void 0 && id !== null) return String(id);
|
|
110
|
+
this._warnMissingRowId();
|
|
111
|
+
return `#${index}`;
|
|
112
|
+
}
|
|
113
|
+
_warnMissingRowId() {
|
|
114
|
+
if (this._warnedMissingRowId) return;
|
|
115
|
+
this._warnedMissingRowId = true;
|
|
116
|
+
console.warn("[@iyulab/data-components] u-rich-table: 행에 `_id` 가 없어 **위치**로 식별합니다. 데이터가 재정렬·재페이징되면 선택 상태가 다른 행으로 옮겨갑니다. 각 행에 고유한 `_id` 를 부여하세요.");
|
|
117
|
+
}
|
|
91
118
|
getSelectedRows() {
|
|
92
|
-
return this.data.filter((row) => this.selectedIds.has(row
|
|
119
|
+
return this.data.filter((row, i) => this.selectedIds.has(this._rowId(row, i)));
|
|
93
120
|
}
|
|
94
121
|
render() {
|
|
95
122
|
return html`
|
|
@@ -122,7 +149,7 @@ var URichTable = class URichTable extends LitElement {
|
|
|
122
149
|
<div style="flex:1"></div>
|
|
123
150
|
<slot name="toolbar-end"></slot>
|
|
124
151
|
${this.addable ? html`
|
|
125
|
-
<button class="btn btn-success" @click=${this._onAddRowClick}
|
|
152
|
+
<button class="btn btn-success" @click=${this._onAddRowClick}>${this.addRowLabel}</button>
|
|
126
153
|
` : ""}
|
|
127
154
|
</div>
|
|
128
155
|
`;
|
|
@@ -157,10 +184,10 @@ var URichTable = class URichTable extends LitElement {
|
|
|
157
184
|
${this.columns.map((col) => html`
|
|
158
185
|
<td>
|
|
159
186
|
${col.filterable !== false ? col.filterType === "select" && col.options ? html`<select @change=${(e) => this._onFilterChange(col.key, e.target.value)}>
|
|
160
|
-
<option value=""
|
|
187
|
+
<option value="">${this.filterAllLabel}</option>
|
|
161
188
|
${col.options.map((o) => html`<option value=${o.value}>${o.label}</option>`)}
|
|
162
189
|
</select>` : html`<input
|
|
163
|
-
placeholder
|
|
190
|
+
placeholder=${this.filterPlaceholder}
|
|
164
191
|
@input=${(e) => this._onFilterChange(col.key, e.target.value)} />` : ""}
|
|
165
192
|
</td>
|
|
166
193
|
`)}
|
|
@@ -169,10 +196,10 @@ var URichTable = class URichTable extends LitElement {
|
|
|
169
196
|
`;
|
|
170
197
|
}
|
|
171
198
|
_renderBody() {
|
|
172
|
-
if (this.loading) return html`<tr><td colspan=${this._colSpan()}><div class="loading-overlay"
|
|
199
|
+
if (this.loading) return html`<tr><td colspan=${this._colSpan()}><div class="loading-overlay">${this.loadingMessage}</div></td></tr>`;
|
|
173
200
|
if (this.data.length === 0) return html`<tr><td colspan=${this._colSpan()}><div class="empty-message">${this.emptyMessage}</div></td></tr>`;
|
|
174
201
|
return this.data.map((row, rowIdx) => {
|
|
175
|
-
const rowId = row
|
|
202
|
+
const rowId = this._rowId(row, rowIdx);
|
|
176
203
|
const isSelected = this.selectedIds.has(rowId);
|
|
177
204
|
const isExpanded = this.expandedIds.has(rowId);
|
|
178
205
|
const hasError = this.rowErrors.has(rowId);
|
|
@@ -192,7 +219,7 @@ var URichTable = class URichTable extends LitElement {
|
|
|
192
219
|
` : ""}
|
|
193
220
|
${this.columns.map((col, colIdx) => this._renderCell(row, rowIdx, col, colIdx))}
|
|
194
221
|
<td class="actions-cell">
|
|
195
|
-
<span
|
|
222
|
+
<span class="row-menu" @click=${() => this._onRowMenu(row)}>⋯</span>
|
|
196
223
|
</td>
|
|
197
224
|
</tr>
|
|
198
225
|
${isExpanded && this.detailRenderer ? html`
|
|
@@ -201,7 +228,7 @@ var URichTable = class URichTable extends LitElement {
|
|
|
201
228
|
</tr>
|
|
202
229
|
` : ""}
|
|
203
230
|
${hasError ? html`
|
|
204
|
-
<tr><td colspan=${this._colSpan()}
|
|
231
|
+
<tr><td colspan=${this._colSpan()} class="row-error-cell">
|
|
205
232
|
${this.rowErrors.get(rowId)}
|
|
206
233
|
</td></tr>
|
|
207
234
|
` : ""}
|
|
@@ -264,7 +291,7 @@ var URichTable = class URichTable extends LitElement {
|
|
|
264
291
|
_renderNewRow() {
|
|
265
292
|
return html`
|
|
266
293
|
<tr class="new-row">
|
|
267
|
-
${this.selectable ? html`<td class="checkbox-cell"><span
|
|
294
|
+
${this.selectable ? html`<td class="checkbox-cell"><span class="new-row-marker">+</span></td>` : ""}
|
|
268
295
|
${this.expandable ? html`<td></td>` : ""}
|
|
269
296
|
${this.columns.map((col, colIdx) => html`
|
|
270
297
|
<td>
|
|
@@ -287,7 +314,7 @@ var URichTable = class URichTable extends LitElement {
|
|
|
287
314
|
const end = Math.min(this.currentPage * this.pageSize, this.totalCount);
|
|
288
315
|
return html`
|
|
289
316
|
<div class="pagination">
|
|
290
|
-
<span
|
|
317
|
+
<span>${this.pageInfoFormatter(this.totalCount, start, end)}</span>
|
|
291
318
|
<div class="page-buttons">
|
|
292
319
|
<button ?disabled=${this.currentPage <= 1} @click=${() => this._onPageChange(this.currentPage - 1)}>◀</button>
|
|
293
320
|
${this._getPageNumbers(totalPages).map((p) => html`
|
|
@@ -306,7 +333,7 @@ var URichTable = class URichTable extends LitElement {
|
|
|
306
333
|
`;
|
|
307
334
|
}
|
|
308
335
|
_onSelectAll(e) {
|
|
309
|
-
if (e.target.checked) this.selectedIds = new Set(this.data.map((r) => r
|
|
336
|
+
if (e.target.checked) this.selectedIds = new Set(this.data.map((r, i) => this._rowId(r, i)));
|
|
310
337
|
else this.selectedIds = /* @__PURE__ */ new Set();
|
|
311
338
|
this._fireSelectionChange();
|
|
312
339
|
}
|
|
@@ -322,7 +349,7 @@ var URichTable = class URichTable extends LitElement {
|
|
|
322
349
|
const start = Math.min(this._lastSelectedIndex, rowIdx);
|
|
323
350
|
const end = Math.max(this._lastSelectedIndex, rowIdx);
|
|
324
351
|
const next = new Set(this.selectedIds);
|
|
325
|
-
for (let i = start; i <= end; i++) next.add(this.data[i]
|
|
352
|
+
for (let i = start; i <= end; i++) next.add(this._rowId(this.data[i], i));
|
|
326
353
|
this.selectedIds = next;
|
|
327
354
|
this._fireSelectionChange();
|
|
328
355
|
}
|
|
@@ -445,7 +472,7 @@ var URichTable = class URichTable extends LitElement {
|
|
|
445
472
|
this.expandedIds = next;
|
|
446
473
|
this.dispatchEvent(new CustomEvent("row-expand", {
|
|
447
474
|
detail: {
|
|
448
|
-
row: this.data.find((r) => r
|
|
475
|
+
row: this.data.find((r, i) => this._rowId(r, i) === rowId),
|
|
449
476
|
expanded
|
|
450
477
|
},
|
|
451
478
|
bubbles: true,
|
|
@@ -588,7 +615,7 @@ var URichTable = class URichTable extends LitElement {
|
|
|
588
615
|
};
|
|
589
616
|
}
|
|
590
617
|
_selectAll() {
|
|
591
|
-
this.selectedIds = new Set(this.data.map((r) => r
|
|
618
|
+
this.selectedIds = new Set(this.data.map((r, i) => this._rowId(r, i)));
|
|
592
619
|
this._fireSelectionChange();
|
|
593
620
|
}
|
|
594
621
|
static define(tagName = "u-rich-table") {
|
|
@@ -602,6 +629,11 @@ __decorate([property({ type: Number }), __decorateMetadata("design:type", Object
|
|
|
602
629
|
__decorate([property({ type: Number }), __decorateMetadata("design:type", Object)], URichTable.prototype, "currentPage", void 0);
|
|
603
630
|
__decorate([property({ type: Boolean }), __decorateMetadata("design:type", Object)], URichTable.prototype, "loading", void 0);
|
|
604
631
|
__decorate([property({ type: String }), __decorateMetadata("design:type", Object)], URichTable.prototype, "emptyMessage", void 0);
|
|
632
|
+
__decorate([property({ type: String }), __decorateMetadata("design:type", Object)], URichTable.prototype, "loadingMessage", void 0);
|
|
633
|
+
__decorate([property({ type: String }), __decorateMetadata("design:type", Object)], URichTable.prototype, "filterPlaceholder", void 0);
|
|
634
|
+
__decorate([property({ type: String }), __decorateMetadata("design:type", Object)], URichTable.prototype, "filterAllLabel", void 0);
|
|
635
|
+
__decorate([property({ type: String }), __decorateMetadata("design:type", Object)], URichTable.prototype, "addRowLabel", void 0);
|
|
636
|
+
__decorate([property({ attribute: false }), __decorateMetadata("design:type", Function)], URichTable.prototype, "pageInfoFormatter", void 0);
|
|
605
637
|
__decorate([property({ type: Boolean }), __decorateMetadata("design:type", Object)], URichTable.prototype, "selectable", void 0);
|
|
606
638
|
__decorate([property({ type: Boolean }), __decorateMetadata("design:type", Object)], URichTable.prototype, "editable", void 0);
|
|
607
639
|
__decorate([property({ type: Boolean }), __decorateMetadata("design:type", Object)], URichTable.prototype, "addable", void 0);
|
|
@@ -5,7 +5,8 @@ var richTableStyles = css`
|
|
|
5
5
|
display: block;
|
|
6
6
|
font-family: system-ui, -apple-system, sans-serif;
|
|
7
7
|
font-size: 13px;
|
|
8
|
-
|
|
8
|
+
color: var(--u-txt-color, #0f172a);
|
|
9
|
+
border: 1px solid var(--u-border-color, #e2e8f0);
|
|
9
10
|
border-radius: 8px;
|
|
10
11
|
overflow: hidden;
|
|
11
12
|
}
|
|
@@ -15,25 +16,25 @@ var richTableStyles = css`
|
|
|
15
16
|
align-items: center;
|
|
16
17
|
gap: 8px;
|
|
17
18
|
padding: 8px 12px;
|
|
18
|
-
background: #f8fafc;
|
|
19
|
-
border-bottom: 1px solid #e2e8f0;
|
|
19
|
+
background: var(--u-neutral-50, #f8fafc);
|
|
20
|
+
border-bottom: 1px solid var(--u-border-color, #e2e8f0);
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
.toolbar .selection-info {
|
|
23
24
|
font-size: 12px;
|
|
24
|
-
color: #
|
|
25
|
+
color: var(--u-txt-color-weak, #757575);
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
.toolbar .search-input {
|
|
28
29
|
padding: 4px 10px;
|
|
29
|
-
border: 1px solid #d1d5db;
|
|
30
|
+
border: 1px solid var(--u-input-border-color, #d1d5db);
|
|
30
31
|
border-radius: 4px;
|
|
31
32
|
font-size: 12px;
|
|
32
33
|
outline: none;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
.toolbar .search-input:focus {
|
|
36
|
-
border-color: #3b82f6;
|
|
37
|
+
border-color: var(--u-input-border-color-focus, #3b82f6);
|
|
37
38
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -43,13 +44,16 @@ var richTableStyles = css`
|
|
|
43
44
|
border-radius: 4px;
|
|
44
45
|
font-size: 12px;
|
|
45
46
|
cursor: pointer;
|
|
47
|
+
/* 예외 — "색 배경 위의 글자" 역할 토큰이 없다. --u-txt-color-inverse 는
|
|
48
|
+
다크에서 neutral-100(#121212) 이라 파랑/초록 버튼 위에서 읽히지 않는다.
|
|
49
|
+
버튼 배경은 두 테마 모두 유채색이므로 흰 글자가 맞다. */
|
|
46
50
|
color: white;
|
|
47
51
|
}
|
|
48
52
|
|
|
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
|
+
.toolbar .btn-primary { background: var(--u-primary-color, #3b82f6); }
|
|
54
|
+
.toolbar .btn-primary:hover { background: var(--u-primary-color-strong, #2563eb); }
|
|
55
|
+
.toolbar .btn-success { background: var(--u-success-color, #10b981); }
|
|
56
|
+
.toolbar .btn-success:hover { background: var(--u-success-color-strong, #059669); }
|
|
53
57
|
|
|
54
58
|
table {
|
|
55
59
|
width: 100%;
|
|
@@ -58,10 +62,10 @@ var richTableStyles = css`
|
|
|
58
62
|
|
|
59
63
|
thead th {
|
|
60
64
|
padding: 8px;
|
|
61
|
-
background: #f1f5f9;
|
|
65
|
+
background: var(--u-neutral-100, #f1f5f9);
|
|
62
66
|
font-weight: 600;
|
|
63
67
|
text-align: left;
|
|
64
|
-
border-bottom: 2px solid #e2e8f0;
|
|
68
|
+
border-bottom: 2px solid var(--u-border-color, #e2e8f0);
|
|
65
69
|
user-select: none;
|
|
66
70
|
position: relative;
|
|
67
71
|
}
|
|
@@ -71,55 +75,55 @@ var richTableStyles = css`
|
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
thead th.sortable:hover {
|
|
74
|
-
background: #e2e8f0;
|
|
78
|
+
background: var(--u-bg-color-active, #e2e8f0);
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
.sort-indicator {
|
|
78
82
|
font-size: 10px;
|
|
79
|
-
color: #94a3b8;
|
|
83
|
+
color: var(--u-txt-color-weak, #94a3b8);
|
|
80
84
|
margin-left: 4px;
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
.filter-row td {
|
|
84
88
|
padding: 4px;
|
|
85
|
-
background: #fefce8;
|
|
86
|
-
border-bottom: 1px solid #e2e8f0;
|
|
89
|
+
background: var(--u-yellow-0, #fefce8);
|
|
90
|
+
border-bottom: 1px solid var(--u-border-color, #e2e8f0);
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
.filter-row input,
|
|
90
94
|
.filter-row select {
|
|
91
95
|
width: 100%;
|
|
92
96
|
padding: 2px 6px;
|
|
93
|
-
border: 1px solid #d1d5db;
|
|
97
|
+
border: 1px solid var(--u-input-border-color, #d1d5db);
|
|
94
98
|
border-radius: 3px;
|
|
95
99
|
font-size: 11px;
|
|
96
100
|
outline: none;
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
tbody tr {
|
|
100
|
-
border-bottom: 1px solid #e2e8f0;
|
|
104
|
+
border-bottom: 1px solid var(--u-border-color-weak, #e2e8f0);
|
|
101
105
|
}
|
|
102
106
|
|
|
103
107
|
tbody tr:hover {
|
|
104
|
-
background: #f8fafc;
|
|
108
|
+
background: var(--u-bg-color-hover, #f8fafc);
|
|
105
109
|
}
|
|
106
110
|
|
|
107
111
|
tbody tr.selected {
|
|
108
|
-
background: #
|
|
112
|
+
background: var(--u-primary-bg-color, #E3F2FD);
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
tbody tr.focused td.focused-cell {
|
|
112
|
-
outline: 2px solid #3b82f6;
|
|
116
|
+
outline: 2px solid var(--u-primary-color, #3b82f6);
|
|
113
117
|
outline-offset: -2px;
|
|
114
118
|
}
|
|
115
119
|
|
|
116
120
|
tbody tr.editing {
|
|
117
|
-
background: #fefce8;
|
|
118
|
-
outline: 2px solid #eab308;
|
|
121
|
+
background: var(--u-yellow-0, #fefce8);
|
|
122
|
+
outline: 2px solid var(--u-warning-color-weak, #eab308);
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
tbody tr.error {
|
|
122
|
-
background: #
|
|
126
|
+
background: var(--u-danger-bg-color, #FFEBEE);
|
|
123
127
|
}
|
|
124
128
|
|
|
125
129
|
tbody td {
|
|
@@ -129,19 +133,19 @@ var richTableStyles = css`
|
|
|
129
133
|
tbody td .cell-edit-input {
|
|
130
134
|
width: 100%;
|
|
131
135
|
padding: 4px 6px;
|
|
132
|
-
border: 1px solid #3b82f6;
|
|
136
|
+
border: 1px solid var(--u-primary-color, #3b82f6);
|
|
133
137
|
border-radius: 3px;
|
|
134
138
|
font-size: 13px;
|
|
135
139
|
outline: none;
|
|
136
140
|
}
|
|
137
141
|
|
|
138
142
|
tbody td .cell-edit-input.invalid {
|
|
139
|
-
border-color: #ef4444;
|
|
143
|
+
border-color: var(--u-danger-color, #ef4444);
|
|
140
144
|
}
|
|
141
145
|
|
|
142
146
|
.validation-error {
|
|
143
147
|
font-size: 10px;
|
|
144
|
-
color: #ef4444;
|
|
148
|
+
color: var(--u-danger-color, #ef4444);
|
|
145
149
|
margin-top: 2px;
|
|
146
150
|
}
|
|
147
151
|
|
|
@@ -154,11 +158,11 @@ var richTableStyles = css`
|
|
|
154
158
|
width: 30px;
|
|
155
159
|
text-align: center;
|
|
156
160
|
cursor: pointer;
|
|
157
|
-
color: #94a3b8;
|
|
161
|
+
color: var(--u-txt-color-weak, #94a3b8);
|
|
158
162
|
}
|
|
159
163
|
|
|
160
164
|
.expand-cell:hover {
|
|
161
|
-
color: #3b82f6;
|
|
165
|
+
color: var(--u-primary-color, #3b82f6);
|
|
162
166
|
}
|
|
163
167
|
|
|
164
168
|
.actions-cell {
|
|
@@ -168,31 +172,31 @@ var richTableStyles = css`
|
|
|
168
172
|
|
|
169
173
|
.new-row td {
|
|
170
174
|
padding: 4px 8px;
|
|
171
|
-
background: #
|
|
175
|
+
background: var(--u-success-bg-color, #E8F5E9);
|
|
172
176
|
opacity: 0.8;
|
|
173
177
|
}
|
|
174
178
|
|
|
175
179
|
.new-row input {
|
|
176
180
|
width: 100%;
|
|
177
181
|
padding: 4px 6px;
|
|
178
|
-
border: 1px dashed #86efac;
|
|
182
|
+
border: 1px dashed var(--u-success-color-weaker, #86efac);
|
|
179
183
|
border-radius: 3px;
|
|
180
184
|
background: transparent;
|
|
181
185
|
font-size: 12px;
|
|
182
|
-
color: #
|
|
186
|
+
color: var(--u-txt-color-weak, #757575);
|
|
183
187
|
outline: none;
|
|
184
188
|
}
|
|
185
189
|
|
|
186
190
|
.new-row input:focus {
|
|
187
|
-
border-color: #10b981;
|
|
188
|
-
background: white;
|
|
191
|
+
border-color: var(--u-success-color, #10b981);
|
|
192
|
+
background: var(--u-bg-color, white);
|
|
189
193
|
opacity: 1;
|
|
190
194
|
}
|
|
191
195
|
|
|
192
196
|
.detail-row td {
|
|
193
197
|
padding: 8px 8px 8px 70px;
|
|
194
|
-
background: #f8fafc;
|
|
195
|
-
border-bottom: 2px solid #e2e8f0;
|
|
198
|
+
background: var(--u-neutral-50, #f8fafc);
|
|
199
|
+
border-bottom: 2px solid var(--u-border-color, #e2e8f0);
|
|
196
200
|
}
|
|
197
201
|
|
|
198
202
|
.badge {
|
|
@@ -208,10 +212,10 @@ var richTableStyles = css`
|
|
|
208
212
|
justify-content: space-between;
|
|
209
213
|
align-items: center;
|
|
210
214
|
padding: 8px 12px;
|
|
211
|
-
background: #f8fafc;
|
|
212
|
-
border-top: 1px solid #e2e8f0;
|
|
215
|
+
background: var(--u-neutral-50, #f8fafc);
|
|
216
|
+
border-top: 1px solid var(--u-border-color, #e2e8f0);
|
|
213
217
|
font-size: 12px;
|
|
214
|
-
color: #
|
|
218
|
+
color: var(--u-txt-color-weak, #757575);
|
|
215
219
|
}
|
|
216
220
|
|
|
217
221
|
.pagination .page-buttons {
|
|
@@ -222,22 +226,22 @@ var richTableStyles = css`
|
|
|
222
226
|
|
|
223
227
|
.pagination button {
|
|
224
228
|
padding: 2px 8px;
|
|
225
|
-
border: 1px solid #d1d5db;
|
|
229
|
+
border: 1px solid var(--u-input-border-color, #d1d5db);
|
|
226
230
|
border-radius: 3px;
|
|
227
|
-
background: white;
|
|
231
|
+
background: var(--u-bg-color, white);
|
|
228
232
|
font-size: 11px;
|
|
229
233
|
cursor: pointer;
|
|
230
234
|
}
|
|
231
235
|
|
|
232
236
|
.pagination button.active {
|
|
233
|
-
background: #3b82f6;
|
|
234
|
-
color: white;
|
|
235
|
-
border-color: #3b82f6;
|
|
237
|
+
background: var(--u-primary-color, #3b82f6);
|
|
238
|
+
color: var(--u-txt-color-inverse, white);
|
|
239
|
+
border-color: var(--u-input-border-color-focus, #3b82f6);
|
|
236
240
|
}
|
|
237
241
|
|
|
238
242
|
.pagination select {
|
|
239
243
|
padding: 2px 4px;
|
|
240
|
-
border: 1px solid #d1d5db;
|
|
244
|
+
border: 1px solid var(--u-input-border-color, #d1d5db);
|
|
241
245
|
border-radius: 3px;
|
|
242
246
|
font-size: 11px;
|
|
243
247
|
margin-left: 8px;
|
|
@@ -248,7 +252,7 @@ var richTableStyles = css`
|
|
|
248
252
|
align-items: center;
|
|
249
253
|
justify-content: center;
|
|
250
254
|
padding: 40px;
|
|
251
|
-
color: #
|
|
255
|
+
color: var(--u-txt-color-weak, #757575);
|
|
252
256
|
}
|
|
253
257
|
|
|
254
258
|
.empty-message {
|
|
@@ -256,177 +260,59 @@ var richTableStyles = css`
|
|
|
256
260
|
align-items: center;
|
|
257
261
|
justify-content: center;
|
|
258
262
|
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);
|
|
263
|
+
color: var(--u-txt-color-weak, #9ca3af);
|
|
311
264
|
}
|
|
312
265
|
|
|
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
266
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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);
|
|
267
|
+
/* 종전에는 아래 셋이 템플릿 안에 style="…#94a3b8" 형태로 박혀 있었다.
|
|
268
|
+
.styles.ts 만 훑는 정리로는 보이지 않는 자리다. */
|
|
269
|
+
.row-menu {
|
|
270
|
+
cursor: pointer;
|
|
271
|
+
color: var(--u-txt-color-weak, #94a3b8);
|
|
347
272
|
}
|
|
348
273
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
274
|
+
.row-error-cell {
|
|
275
|
+
padding: 2px 8px;
|
|
276
|
+
background: var(--u-danger-bg-color, #FFEBEE);
|
|
277
|
+
color: var(--u-danger-color, #ef4444);
|
|
278
|
+
font-size: 11px;
|
|
352
279
|
}
|
|
353
280
|
|
|
354
|
-
|
|
355
|
-
|
|
281
|
+
.new-row-marker {
|
|
282
|
+
color: var(--u-success-color-weaker, #86efac);
|
|
356
283
|
}
|
|
357
284
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
285
|
+
/* ── 다크 보정 ──
|
|
286
|
+
⚠**여기 남은 규칙은 "다크 테마 구현"이 아니다.** 역할 토큰의 중립 계열은 두 테마에서
|
|
287
|
+
서로 다른 팔레트 단에 매핑돼 있어(--u-bg-color = neutral-0 라이트 / neutral-100 다크,
|
|
288
|
+
--u-border-color = neutral-300 / neutral-400) 테마 보정이 토큰 층에 이미 들어 있다.
|
|
289
|
+
그래서 base 규칙 하나로 두 테마가 성립한다.
|
|
363
290
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
291
|
+
종전에는 이 자리에 57개 선언이 있었고, base 레이어가 토큰을 전혀 쓰지 않았기 때문에
|
|
292
|
+
(Tailwind 계열 hex 하드코딩) 다크를 통째로 재구현할 수밖에 없었다. base 를 역할 토큰으로
|
|
293
|
+
옮기면서 그 대부분이 불필요해졌다.
|
|
367
294
|
|
|
368
|
-
|
|
369
|
-
color: var(--u-red-500, #D66060);
|
|
370
|
-
}
|
|
295
|
+
남긴 것은 역할 층에 자리가 없는 하나뿐이다:
|
|
371
296
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
297
|
+
**표면 높이 축이 없다.** 배경 역할 토큰은 상호작용 상태 이름(-hover/-active/
|
|
298
|
+
-disabled)뿐이라 "바탕보다 한 단 올라온 면"을 뜻하는 토큰이 없다. 라이트에서는
|
|
299
|
+
neutral-50/100 이 흰 바탕 위에서 충분히 읽히지만, 다크에서는 그 단이 각각
|
|
300
|
+
#0A0A0A/#121212 로 거의 검정이라 툴바·헤더·페이지네이션이 배경에 묻힌다.
|
|
375
301
|
|
|
376
|
-
:host-context([theme="dark"]) .expand-cell:hover {
|
|
377
|
-
color: var(--u-blue-500, #6ba3e3);
|
|
378
|
-
}
|
|
379
302
|
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
}
|
|
303
|
+
⚠:host-context 는 Firefox/Safari 미지원이다. 그러나 위 base 마이그레이션으로
|
|
304
|
+
**색의 대부분이 이제 토큰 층에서 테마를 따르므로**, 그 브라우저에서도 다크가
|
|
305
|
+
동작한다 — 종전에는 이 블록이 전부였기 때문에 다크가 아예 없었다. */
|
|
399
306
|
|
|
307
|
+
:host-context([theme="dark"]) .toolbar,
|
|
308
|
+
:host-context([theme="dark"]) thead th,
|
|
309
|
+
:host-context([theme="dark"]) .detail-row td,
|
|
400
310
|
:host-context([theme="dark"]) .pagination {
|
|
401
311
|
background: var(--u-neutral-200, #1E1E1E);
|
|
402
|
-
border-top-color: var(--u-border-color, #3D3D3D);
|
|
403
|
-
color: var(--u-txt-color-weak, #8A8A8A);
|
|
404
312
|
}
|
|
405
313
|
|
|
406
314
|
:host-context([theme="dark"]) .pagination button {
|
|
407
315
|
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
316
|
}
|
|
431
317
|
`;
|
|
432
318
|
//#endregion
|
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.
|
|
4
|
+
"version": "0.8.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.
|
|
61
|
+
"@iyulab/components": "^1.15.0",
|
|
62
62
|
"lit": "^3.3.3"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|