@iyulab/components 1.13.0 → 1.14.1
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 +68 -0
- package/dist/components/UElement.styles.js +8 -8
- package/dist/components/UOverlayElement.styles.js +1 -1
- package/dist/components/alert/UAlert.styles.js +21 -21
- package/dist/components/avatar/UAvatar.styles.js +5 -5
- package/dist/components/badge/UBadge.styles.js +22 -22
- package/dist/components/breadcrumb/UBreadcrumb.styles.js +2 -2
- package/dist/components/breadcrumb-item/UBreadcrumbItem.styles.js +2 -2
- package/dist/components/button/UButton.styles.js +28 -28
- package/dist/components/card/UCard.styles.js +5 -5
- package/dist/components/carousel/UCarousel.styles.js +8 -8
- package/dist/components/checkbox/UCheckbox.styles.js +20 -20
- package/dist/components/copy-button/UCopyButton.styles.js +2 -2
- package/dist/components/dialog/UDialog.styles.js +4 -4
- package/dist/components/divider/UDivider.styles.js +2 -2
- package/dist/components/drawer/UDrawer.styles.js +3 -3
- package/dist/components/field/UField.styles.js +4 -4
- package/dist/components/icon-button/UIconButton.styles.js +2 -2
- package/dist/components/input/UInput.styles.js +30 -30
- package/dist/components/menu/UMenu.styles.js +4 -4
- package/dist/components/menu-item/UMenuItem.styles.js +7 -7
- package/dist/components/option/UOption.styles.js +9 -9
- package/dist/components/progress-bar/UProgressBar.styles.js +12 -12
- package/dist/components/progress-ring/UProgressRing.styles.js +11 -11
- package/dist/components/radio/URadio.styles.js +6 -6
- package/dist/components/rating/URating.styles.js +3 -3
- package/dist/components/select/USelect.styles.js +34 -34
- package/dist/components/skeleton/USkeleton.d.ts +2 -2
- package/dist/components/skeleton/USkeleton.styles.js +5 -5
- package/dist/components/slider/USlider.styles.js +10 -10
- package/dist/components/spinner/USpinner.styles.js +11 -11
- package/dist/components/split-panel/USplitPanel.styles.js +3 -3
- package/dist/components/switch/USwitch.styles.js +10 -10
- package/dist/components/tab/UTab.styles.js +3 -3
- package/dist/components/tab-panel/UTabPanel.styles.js +22 -22
- package/dist/components/tag/UTag.styles.js +55 -55
- package/dist/components/textarea/UTextarea.styles.js +25 -25
- package/dist/components/tooltip/UTooltip.styles.js +3 -3
- package/dist/components/tree/UTree.d.ts +1 -1
- package/dist/components/tree/UTree.styles.js +6 -3
- package/dist/components/tree-item/UTreeItem.styles.js +7 -7
- package/dist/plugins/vite-plugin-glob-resolve.d.ts +12 -0
- package/dist/plugins/vite-plugin-glob-resolve.js +66 -0
- package/dist/plugins/vite-plugin-react-wrapper.d.ts +54 -0
- package/dist/plugins/vite-plugin-react-wrapper.js +294 -0
- package/dist/utilities/Theme.d.ts +16 -1
- package/dist/utilities/Theme.js +22 -1
- package/package.json +5 -6
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { resolve, join, relative, dirname, basename } from 'path';
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
3
|
+
import { gzipSync } from 'zlib';
|
|
4
|
+
import { globSync } from 'glob';
|
|
5
|
+
/**
|
|
6
|
+
* Lit Element 컴포넌트를 React 래퍼로 자동 생성하는 Vite 플러그인
|
|
7
|
+
*/
|
|
8
|
+
export default function reactWrapperPlugin(options) {
|
|
9
|
+
let rootDir;
|
|
10
|
+
let buildOutDir;
|
|
11
|
+
let outDir;
|
|
12
|
+
return {
|
|
13
|
+
name: 'vite-plugin-react-wrapper',
|
|
14
|
+
configResolved(config) {
|
|
15
|
+
rootDir = config.root;
|
|
16
|
+
buildOutDir = resolve(rootDir, config.build.outDir);
|
|
17
|
+
outDir = resolve(buildOutDir, options.output || 'react');
|
|
18
|
+
},
|
|
19
|
+
closeBundle() {
|
|
20
|
+
const log = (msg) => console.log(`\x1b[36m[react-wrapper]\x1b[0m ${msg}`);
|
|
21
|
+
log('Generating React wrappers...');
|
|
22
|
+
// 컴포넌트 수집
|
|
23
|
+
const inputPattern = (options.input || 'src/components') + '/**/*.ts';
|
|
24
|
+
let files = globSync(inputPattern, { cwd: rootDir, absolute: true });
|
|
25
|
+
if (options.exclude?.length) {
|
|
26
|
+
const excluded = new Set(options.exclude.flatMap(p => globSync(p, { cwd: rootDir, absolute: true })));
|
|
27
|
+
files = files.filter((f) => !excluded.has(f));
|
|
28
|
+
}
|
|
29
|
+
// 이벤트 선언 누락은 **빌드를 실패**시킨다. 경고로 두면 사람 기억에 의존하게 되고,
|
|
30
|
+
// 누락된 이벤트는 React 소비자 쪽에서 에러 없이 조용히 죽는다 — 알아채기 가장 어려운
|
|
31
|
+
// 실패 형태다. 전 컴포넌트를 훑은 뒤 위반을 한 번에 보고한다.
|
|
32
|
+
const violations = [];
|
|
33
|
+
const components = files.flatMap((f) => parseComponent(f, violations));
|
|
34
|
+
if (violations.length > 0) {
|
|
35
|
+
throw new Error(`[react-wrapper] 이벤트 선언 누락 ${violations.length}건:\n` +
|
|
36
|
+
violations.map(v => ` - ${v}`).join('\n'));
|
|
37
|
+
}
|
|
38
|
+
if (components.length === 0) {
|
|
39
|
+
log('No components found.');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// 출력 디렉토리 생성
|
|
43
|
+
mkdirSync(outDir, { recursive: true });
|
|
44
|
+
// 래퍼 생성
|
|
45
|
+
const generated = [];
|
|
46
|
+
for (const comp of components) {
|
|
47
|
+
generated.push(...writeWrapper(comp, outDir, buildOutDir));
|
|
48
|
+
}
|
|
49
|
+
generated.push(...writeIndex(components, outDir, buildOutDir));
|
|
50
|
+
// 결과 출력
|
|
51
|
+
generated.sort((a, b) => a.size - b.size);
|
|
52
|
+
for (const f of generated) {
|
|
53
|
+
const kb = (f.size / 1024).toFixed(2);
|
|
54
|
+
const gzKb = (f.gzipSize / 1024).toFixed(2);
|
|
55
|
+
const pad = ' '.repeat(Math.max(0, 50 - f.path.length));
|
|
56
|
+
console.log(`${f.path}${pad}${kb.padStart(6)} kB │ gzip: ${gzKb.padStart(6)} kB`);
|
|
57
|
+
}
|
|
58
|
+
log(`Generated ${components.length} React wrappers.`);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// --- 컴포넌트 파싱 ---
|
|
63
|
+
function parseComponent(filePath, violations = []) {
|
|
64
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
65
|
+
// @customElement('tag-name') 데코레이터 확인
|
|
66
|
+
const tagMatch = content.match(/@customElement\s*\(\s*['"]([^'"]+)['"]\s*\)/);
|
|
67
|
+
if (!tagMatch)
|
|
68
|
+
return [];
|
|
69
|
+
// export class ClassName 추출
|
|
70
|
+
const classMatch = content.match(/export\s+(?:abstract\s+)?class\s+(\w+)/);
|
|
71
|
+
if (!classMatch)
|
|
72
|
+
return [];
|
|
73
|
+
const events = collectComponentEvents(filePath);
|
|
74
|
+
violations.push(...findEventDeclarationViolations(content, classMatch[1], tagMatch[1], events.length));
|
|
75
|
+
return [{
|
|
76
|
+
className: classMatch[1],
|
|
77
|
+
tagName: tagMatch[1],
|
|
78
|
+
filePath,
|
|
79
|
+
events,
|
|
80
|
+
}];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 이벤트가 **정적으로 수집되지 않는 형태**로 발화되는지 검사한다. 반환값이 비어 있지 않으면
|
|
84
|
+
* 빌드를 실패시킨다 — 누락된 이벤트는 React 소비자 쪽에서 에러도 경고도 없이 죽으므로
|
|
85
|
+
* 경고로 두면 아무도 알아채지 못한다.
|
|
86
|
+
*
|
|
87
|
+
* `content` 는 **leaf 파일 본문만**이다 — 베이스까지 훑으면 `UElement` 의 `fire`/`relay`
|
|
88
|
+
* 구현 자체가 걸려 전 컴포넌트가 위반이 된다. 베이스에서 발화하는 이벤트는 상속 수집
|
|
89
|
+
* ({@link collectComponentEvents})이 이미 채우므로 여기서 볼 필요가 없다.
|
|
90
|
+
*/
|
|
91
|
+
export function findEventDeclarationViolations(content, className, tagName, eventCount) {
|
|
92
|
+
const violations = [];
|
|
93
|
+
// relay/dispatchEvent 는 이름을 정적으로 도출할 수 없다. @event 태그도 없으면
|
|
94
|
+
// events 맵이 빈 채로 생성되어 React 소비자가 구독할 방법이 사라진다.
|
|
95
|
+
if (eventCount === 0 && /this\.(relay|dispatchEvent)\s*\(/.test(content)) {
|
|
96
|
+
violations.push(`${className} (${tagName}): 이벤트를 발생시키지만(relay/dispatchEvent) @event 태그가 없어 ` +
|
|
97
|
+
`React 래퍼에 이벤트 prop이 노출되지 않습니다. 클래스 JSDoc에 '@event <name>'을 추가하세요.`);
|
|
98
|
+
}
|
|
99
|
+
// this.fire(변수) 처럼 이름이 리터럴이 아니면 역시 수집되지 않는다.
|
|
100
|
+
for (const m of content.matchAll(/this\.fire\s*(?:<[^>]+>)?\s*\(\s*([^'"\s)])/g)) {
|
|
101
|
+
violations.push(`${className} (${tagName}): this.fire(${m[1]}…) 의 이벤트명이 문자열 리터럴이 아니라 ` +
|
|
102
|
+
`정적으로 수집할 수 없습니다. 클래스 JSDoc에 '@event <name>'을 추가하세요.`);
|
|
103
|
+
}
|
|
104
|
+
return violations;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 컴포넌트가 노출하는 이벤트를 **상속 체인 전체**에서 수집한다.
|
|
108
|
+
*
|
|
109
|
+
* leaf 파일만 읽으면 베이스 클래스가 발화하는 이벤트를 놓친다 — 예를 들어 `UDialog`·
|
|
110
|
+
* `UDrawer` 는 `show`/`hide` 를 **`UOverlayElement` 가** 발화하므로, leaf 만 보면
|
|
111
|
+
* `events: {}` 가 생성되고 React 소비자는 그 이벤트를 **구독할 방법이 없다**(에러도 경고도
|
|
112
|
+
* 없이 조용히 실패한다).
|
|
113
|
+
*
|
|
114
|
+
* 수집 순서는 **베이스 → leaf**다. `@event` 는 이름만 주고(detail=unknown) 기존 항목을
|
|
115
|
+
* 덮지 않는 반면 `this.fire<T>('name')` 은 타입까지 주며 덮으므로, 이 순서에서
|
|
116
|
+
* ⑴베이스의 타입 정보가 leaf 의 `@event` 재선언에 지워지지 않고
|
|
117
|
+
* ⑵leaf 가 같은 이벤트를 더 구체적 타입으로 발화하면 그것이 이긴다.
|
|
118
|
+
*/
|
|
119
|
+
export function collectComponentEvents(filePath) {
|
|
120
|
+
const eventMap = new Map();
|
|
121
|
+
collectInto(eventMap, filePath, new Set());
|
|
122
|
+
return Array.from(eventMap, ([name, { detailType, detailSource }]) => ({
|
|
123
|
+
name,
|
|
124
|
+
reactName: toCamelEvent(name),
|
|
125
|
+
detailType,
|
|
126
|
+
detailSource,
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
function collectInto(eventMap, filePath, visited) {
|
|
130
|
+
if (visited.has(filePath) || !existsSync(filePath))
|
|
131
|
+
return; // 순환 상속/누락 파일 방어
|
|
132
|
+
visited.add(filePath);
|
|
133
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
134
|
+
// import 구문에서 식별자 → 소스 경로 매핑 수집 (detail 타입과 베이스 클래스 양쪽에 쓴다)
|
|
135
|
+
// 예: import { ShowEventDetail } from '../events/ShowEvent.js';
|
|
136
|
+
const importMap = new Map();
|
|
137
|
+
for (const m of content.matchAll(/import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g)) {
|
|
138
|
+
const importPath = m[2];
|
|
139
|
+
for (const name of m[1].split(',')) {
|
|
140
|
+
const typeName = name.replace(/type\s+/, '').trim();
|
|
141
|
+
if (typeName)
|
|
142
|
+
importMap.set(typeName, importPath);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// 베이스 클래스를 먼저 수집한다 (제네릭 베이스 `extends UFormControlElement<T>` 포함)
|
|
146
|
+
const extendsMatch = content.match(/export\s+(?:abstract\s+)?class\s+\w+(?:<[^>]*>)?\s+extends\s+(\w+)/);
|
|
147
|
+
if (extendsMatch) {
|
|
148
|
+
const basePath = resolveLocalModule(filePath, importMap.get(extendsMatch[1]));
|
|
149
|
+
if (basePath)
|
|
150
|
+
collectInto(eventMap, basePath, visited);
|
|
151
|
+
}
|
|
152
|
+
// JSDoc @event 패턴: @event eventName — 이름만 제공하므로 기존 항목을 덮지 않는다
|
|
153
|
+
for (const m of content.matchAll(/@event\s+([\w-]+)/g)) {
|
|
154
|
+
if (!eventMap.has(m[1]))
|
|
155
|
+
eventMap.set(m[1], { detailType: 'unknown', detailSource: '' });
|
|
156
|
+
}
|
|
157
|
+
// this.fire<DetailType>('eventName') 패턴 - 제네릭 타입 추출
|
|
158
|
+
for (const m of content.matchAll(/this\.fire\s*(?:<([^>]+)>)?\s*\(\s*['"]([\w-]+)['"]/g)) {
|
|
159
|
+
const detailType = m[1] || 'unknown';
|
|
160
|
+
const detailSource = resolveLocalModule(filePath, importMap.get(detailType)) || '';
|
|
161
|
+
eventMap.set(m[2], { detailType, detailSource });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** 같은 패키지 소스 안의 상대 import 를 절대 `.ts` 경로로 해석한다 (외부 모듈이면 null) */
|
|
165
|
+
function resolveLocalModule(fromFile, importPath) {
|
|
166
|
+
if (!importPath || !importPath.startsWith('.'))
|
|
167
|
+
return null;
|
|
168
|
+
const abs = resolve(dirname(fromFile), importPath.replace(/\.js$/, '.ts'));
|
|
169
|
+
return existsSync(abs) ? abs : null;
|
|
170
|
+
}
|
|
171
|
+
/** kebab-case 이벤트명을 onCamelCase로 변환 (예: shift-start → onShiftStart) */
|
|
172
|
+
function toCamelEvent(name) {
|
|
173
|
+
return 'on' + name.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join('');
|
|
174
|
+
}
|
|
175
|
+
function writeFile(filePath, content, buildOutDir) {
|
|
176
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
177
|
+
return {
|
|
178
|
+
path: relative(buildOutDir, filePath).replace(/\\/g, '/'),
|
|
179
|
+
size: Buffer.byteLength(content, 'utf-8'),
|
|
180
|
+
gzipSize: gzipSync(content).length,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function computeImportPath(from, to) {
|
|
184
|
+
let rel = relative(dirname(from), to).replace(/\\/g, '/').replace(/\.ts$/, '');
|
|
185
|
+
if (!rel.startsWith('.'))
|
|
186
|
+
rel = './' + rel;
|
|
187
|
+
return rel;
|
|
188
|
+
}
|
|
189
|
+
function writeWrapper(comp, outDir, buildOutDir) {
|
|
190
|
+
const { className, tagName, events } = comp;
|
|
191
|
+
const jsPath = join(outDir, `${className}.js`);
|
|
192
|
+
const dtsPath = join(outDir, `${className}.d.ts`);
|
|
193
|
+
// 소스 파일의 빌드 출력 경로 추정 (src/ → 빌드 outDir 매핑)
|
|
194
|
+
const srcIndex = comp.filePath.replace(/\\/g, '/').indexOf('/src/');
|
|
195
|
+
const relFromSrc = srcIndex >= 0
|
|
196
|
+
? comp.filePath.substring(srcIndex + 5).replace(/\\/g, '/').replace(/\.ts$/, '')
|
|
197
|
+
: basename(comp.filePath, '.ts');
|
|
198
|
+
const builtModulePath = resolve(buildOutDir, relFromSrc + '.js');
|
|
199
|
+
const importPath = computeImportPath(jsPath, builtModulePath);
|
|
200
|
+
// events 객체 생성
|
|
201
|
+
const eventsObj = events.length > 0
|
|
202
|
+
? `{\n${events.map(e => ` ${e.reactName}: '${e.name}',`).join('\n')}\n }`
|
|
203
|
+
: '{}';
|
|
204
|
+
// .js
|
|
205
|
+
const js = `import React from 'react';
|
|
206
|
+
import { createComponent } from '@lit/react';
|
|
207
|
+
import { ${className} as ${className}Element } from '${importPath}';
|
|
208
|
+
|
|
209
|
+
export const ${className} = createComponent({
|
|
210
|
+
react: React,
|
|
211
|
+
tagName: '${tagName}',
|
|
212
|
+
elementClass: ${className}Element,
|
|
213
|
+
events: ${eventsObj},
|
|
214
|
+
});
|
|
215
|
+
`;
|
|
216
|
+
// .d.ts - import 구문 생성
|
|
217
|
+
const dtsImportPath = importPath.replace(/\.js$/, '');
|
|
218
|
+
const dtsImports = [
|
|
219
|
+
`import React from 'react';`,
|
|
220
|
+
`import { ${className} as ${className}Element } from '${dtsImportPath}';`,
|
|
221
|
+
];
|
|
222
|
+
// 이벤트 detail 타입별 import - 소스 원본 경로 기준으로 빌드 경로 계산.
|
|
223
|
+
// detailSource 는 **선언한 파일 기준으로 이미 절대화**돼 있다(상속받은 이벤트의 경로가
|
|
224
|
+
// leaf 기준으로 잘못 풀리는 것을 막기 위함 — ComponentEvent.detailSource 주석 참조).
|
|
225
|
+
const detailImportMap = new Map(); // 빌드경로 → [타입명]
|
|
226
|
+
for (const e of events) {
|
|
227
|
+
if (e.detailType === 'unknown' || !e.detailSource)
|
|
228
|
+
continue;
|
|
229
|
+
const absEventSrc = e.detailSource.replace(/\\/g, '/');
|
|
230
|
+
const eventSrcIndex = absEventSrc.indexOf('/src/');
|
|
231
|
+
const relFromSrc = eventSrcIndex >= 0
|
|
232
|
+
? absEventSrc.substring(eventSrcIndex + 5).replace(/\.ts$/, '')
|
|
233
|
+
: '';
|
|
234
|
+
if (!relFromSrc)
|
|
235
|
+
continue;
|
|
236
|
+
const absEventBuilt = resolve(buildOutDir, relFromSrc);
|
|
237
|
+
const eventImportPath = computeImportPath(dtsPath, absEventBuilt + '.js').replace(/\.js$/, '');
|
|
238
|
+
const types = detailImportMap.get(eventImportPath) || [];
|
|
239
|
+
if (!types.includes(e.detailType))
|
|
240
|
+
types.push(e.detailType);
|
|
241
|
+
detailImportMap.set(eventImportPath, types);
|
|
242
|
+
}
|
|
243
|
+
for (const [path, types] of detailImportMap) {
|
|
244
|
+
dtsImports.push(`import { type ${types.join(', type ')} } from '${path}';`);
|
|
245
|
+
}
|
|
246
|
+
const eventTypes = events.length > 0
|
|
247
|
+
? events.map(e => {
|
|
248
|
+
const type = e.detailType !== 'unknown' ? `CustomEvent<${e.detailType}>` : 'CustomEvent';
|
|
249
|
+
return ` ${e.reactName}?: (event: ${type}) => void;`;
|
|
250
|
+
}).join('\n') + '\n'
|
|
251
|
+
: '';
|
|
252
|
+
// 래퍼 이벤트 prop(onChange 등)은 React.HTMLAttributes 의 동명 핸들러와 교집합되면
|
|
253
|
+
// 어떤 시그니처도 대입 불가가 된다. HTMLAttributes 쪽에서 해당 키를 제거해 CustomEvent
|
|
254
|
+
// 시그니처만 남긴다. 이벤트가 없으면 Omit<…, never> 로 HTMLAttributes 원형 유지.
|
|
255
|
+
const eventKeyUnion = events.length > 0
|
|
256
|
+
? events.map(e => `'${e.reactName}'`).join(' | ')
|
|
257
|
+
: 'never';
|
|
258
|
+
// Partial<Element> 은 DOM 프로퍼티(children: HTMLCollection 등)를 포함해 React 의 JSX
|
|
259
|
+
// children/이벤트/className 등과 충돌한다. keyof React.HTMLAttributes 를 제거해 컴포넌트
|
|
260
|
+
// 고유 prop만 남기고, React 친화 타입(children: ReactNode 포함)은 HTMLAttributes 가 제공한다.
|
|
261
|
+
//
|
|
262
|
+
// `React.RefAttributes` 를 반드시 교집합에 넣는다 — `ForwardRefExoticComponent<P>` 는 P 에
|
|
263
|
+
// ref 를 자동으로 더해 주지 않는다. @lit/react 의 `ReactWebComponent` 자신이
|
|
264
|
+
// `PropsWithoutRef<…> & React.RefAttributes<I>` 로 선언돼 있으므로(런타임은 ref 를 전달한다),
|
|
265
|
+
// 이것을 빠뜨리면 **런타임은 되는데 타입만 거부하는** 괴리가 생긴다.
|
|
266
|
+
const dts = `${dtsImports.join('\n')}
|
|
267
|
+
|
|
268
|
+
export declare const ${className}: React.ForwardRefExoticComponent<
|
|
269
|
+
Omit<Partial<${className}Element>, keyof React.HTMLAttributes<${className}Element>>
|
|
270
|
+
& Omit<React.HTMLAttributes<${className}Element>, ${eventKeyUnion}>
|
|
271
|
+
& React.RefAttributes<${className}Element>
|
|
272
|
+
& {
|
|
273
|
+
${eventTypes} }
|
|
274
|
+
>;
|
|
275
|
+
|
|
276
|
+
export type ${className}Props = React.ComponentProps<typeof ${className}>;
|
|
277
|
+
`;
|
|
278
|
+
return [
|
|
279
|
+
writeFile(jsPath, js, buildOutDir),
|
|
280
|
+
writeFile(dtsPath, dts, buildOutDir),
|
|
281
|
+
];
|
|
282
|
+
}
|
|
283
|
+
function writeIndex(components, outDir, buildOutDir) {
|
|
284
|
+
const jsExports = components
|
|
285
|
+
.map(c => `export { ${c.className} } from './${c.className}.js';`)
|
|
286
|
+
.join('\n');
|
|
287
|
+
const dtsExports = components
|
|
288
|
+
.map(c => `export { ${c.className}, ${c.className}Props } from './${c.className}';`)
|
|
289
|
+
.join('\n');
|
|
290
|
+
return [
|
|
291
|
+
writeFile(join(outDir, 'index.js'), jsExports + '\n', buildOutDir),
|
|
292
|
+
writeFile(join(outDir, 'index.d.ts'), dtsExports + '\n', buildOutDir),
|
|
293
|
+
];
|
|
294
|
+
}
|
|
@@ -41,9 +41,24 @@ export declare class Theme {
|
|
|
41
41
|
*/
|
|
42
42
|
static init(options?: ThemeInitOptions): Promise<void>;
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
44
|
+
* 사용자가 **선택한** 테마를 가져옵니다 — `'system'` 을 포함합니다.
|
|
45
|
+
*
|
|
46
|
+
* ⚠**이 값을 밝기 판단에 그대로 쓰지 마십시오.** `'system'` 은 실제로 적용된 색이
|
|
47
|
+
* 아니라 *"OS 를 따른다"* 는 선호이며, 기본값이기도 합니다. 스타일이나 서드파티
|
|
48
|
+
* 에디터 테마를 고를 때는 {@link resolved} 를 쓰십시오.
|
|
45
49
|
*/
|
|
46
50
|
static get(): ThemeType | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* 문서에 **실제로 적용된** 테마를 가져옵니다 — 항상 `'light'` 또는 `'dark'` 입니다.
|
|
53
|
+
*
|
|
54
|
+
* `get()` 과 갈리는 지점은 `'system'` 일 때입니다. 선호가 system 이면 실효 테마는
|
|
55
|
+
* OS 설정에 따라 갈리는데, `get()` 은 그것을 알려주지 못합니다. 그래서 소비자가
|
|
56
|
+
* `get() === 'dark'` 로 분기하면 **system + OS 다크에서 밝은 화면을 그리게 됩니다**
|
|
57
|
+
* — 기본 설정이 system 이라 이 경로가 가장 흔합니다.
|
|
58
|
+
*
|
|
59
|
+
* 판정 순서: `<html theme>`(항상 실효값이 적힘) → 명시 선호 → `prefers-color-scheme`.
|
|
60
|
+
*/
|
|
61
|
+
static resolved(): 'light' | 'dark';
|
|
47
62
|
/**
|
|
48
63
|
* 현재 문서에 적용할 테마를 설정합니다.
|
|
49
64
|
*/
|
package/dist/utilities/Theme.js
CHANGED
|
@@ -83,7 +83,11 @@ var Theme = class {
|
|
|
83
83
|
this.log("theme initialized");
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
|
-
*
|
|
86
|
+
* 사용자가 **선택한** 테마를 가져옵니다 — `'system'` 을 포함합니다.
|
|
87
|
+
*
|
|
88
|
+
* ⚠**이 값을 밝기 판단에 그대로 쓰지 마십시오.** `'system'` 은 실제로 적용된 색이
|
|
89
|
+
* 아니라 *"OS 를 따른다"* 는 선호이며, 기본값이기도 합니다. 스타일이나 서드파티
|
|
90
|
+
* 에디터 테마를 고를 때는 {@link resolved} 를 쓰십시오.
|
|
87
91
|
*/
|
|
88
92
|
static get() {
|
|
89
93
|
switch (document.documentElement.getAttribute("data-theme")) {
|
|
@@ -94,6 +98,23 @@ var Theme = class {
|
|
|
94
98
|
}
|
|
95
99
|
}
|
|
96
100
|
/**
|
|
101
|
+
* 문서에 **실제로 적용된** 테마를 가져옵니다 — 항상 `'light'` 또는 `'dark'` 입니다.
|
|
102
|
+
*
|
|
103
|
+
* `get()` 과 갈리는 지점은 `'system'` 일 때입니다. 선호가 system 이면 실효 테마는
|
|
104
|
+
* OS 설정에 따라 갈리는데, `get()` 은 그것을 알려주지 못합니다. 그래서 소비자가
|
|
105
|
+
* `get() === 'dark'` 로 분기하면 **system + OS 다크에서 밝은 화면을 그리게 됩니다**
|
|
106
|
+
* — 기본 설정이 system 이라 이 경로가 가장 흔합니다.
|
|
107
|
+
*
|
|
108
|
+
* 판정 순서: `<html theme>`(항상 실효값이 적힘) → 명시 선호 → `prefers-color-scheme`.
|
|
109
|
+
*/
|
|
110
|
+
static resolved() {
|
|
111
|
+
const applied = document.documentElement.getAttribute("theme");
|
|
112
|
+
if (applied === "dark" || applied === "light") return applied;
|
|
113
|
+
const preference = this.get();
|
|
114
|
+
if (preference === "dark" || preference === "light") return preference;
|
|
115
|
+
return typeof window !== "undefined" && window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
97
118
|
* 현재 문서에 적용할 테마를 설정합니다.
|
|
98
119
|
*/
|
|
99
120
|
static set(theme) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iyulab/components",
|
|
3
3
|
"description": "web-components library based on lit-element made by iyulab",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.14.1",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"iyulab",
|
|
7
7
|
"components",
|
|
@@ -18,8 +18,6 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"dist",
|
|
20
20
|
"skills",
|
|
21
|
-
"plugins/**/*.js",
|
|
22
|
-
"plugins/**/*.d.ts",
|
|
23
21
|
"package.json",
|
|
24
22
|
"README.md",
|
|
25
23
|
"CHANGELOG.md",
|
|
@@ -36,7 +34,7 @@
|
|
|
36
34
|
"./react": "./dist/react/index.js",
|
|
37
35
|
"./react/*": "./dist/react/*",
|
|
38
36
|
"./styles/*": "./dist/styles/*",
|
|
39
|
-
"./plugins/*": "./plugins/*"
|
|
37
|
+
"./plugins/*": "./dist/plugins/*"
|
|
40
38
|
},
|
|
41
39
|
"scripts": {
|
|
42
40
|
"start": "vite --force",
|
|
@@ -44,12 +42,13 @@
|
|
|
44
42
|
"test:browser": "vitest run --project=browser",
|
|
45
43
|
"lint": "eslint src/",
|
|
46
44
|
"lint:fix": "eslint src/ --fix",
|
|
47
|
-
"build": "eslint && npm run typecheck:plugins && vite build && npm run test:react-types",
|
|
45
|
+
"build": "eslint && npm run typecheck:plugins && vite build && npm run build:plugins && npm run test:react-types",
|
|
48
46
|
"test:react-types": "tsc -p tsconfig.react-smoke.json",
|
|
49
47
|
"typecheck:plugins": "tsc -p plugins/tsconfig.json",
|
|
50
48
|
"docs:cssprops": "node scripts/cssprops-doc.mjs --write",
|
|
51
49
|
"docs:react-events": "node scripts/react-events-doc.mjs --write",
|
|
52
|
-
"docs:tokens": "node scripts/design-tokens-doc.mjs --write"
|
|
50
|
+
"docs:tokens": "node scripts/design-tokens-doc.mjs --write",
|
|
51
|
+
"build:plugins": "tsc -p plugins/tsconfig.build.json"
|
|
53
52
|
},
|
|
54
53
|
"dependencies": {
|
|
55
54
|
"@floating-ui/dom": "^1.8.0",
|