@iyulab/components 0.1.0 → 0.1.2
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 +8 -0
- package/dist/assets/icons/arrow-clockwise.svg.js +3 -7
- package/dist/assets/icons/arrow-down.svg.js +3 -6
- package/dist/assets/icons/arrow-up.svg.js +3 -6
- package/dist/assets/icons/ban.svg.js +3 -6
- package/dist/assets/icons/bell-fill.svg.js +3 -6
- package/dist/assets/icons/check-circle-fill.svg.js +3 -6
- package/dist/assets/icons/check-lg.svg.js +3 -6
- package/dist/assets/icons/chevron-down.svg.js +3 -6
- package/dist/assets/icons/chevron-left.svg.js +3 -6
- package/dist/assets/icons/chevron-right.svg.js +3 -6
- package/dist/assets/icons/chevron-up.svg.js +3 -6
- package/dist/assets/icons/copy.svg.js +3 -6
- package/dist/assets/icons/dash-lg.svg.js +3 -6
- package/dist/assets/icons/exclamation-circle-fill.svg.js +3 -6
- package/dist/assets/icons/exclamation-triangle-fill.svg.js +3 -6
- package/dist/assets/icons/eye-slash.svg.js +3 -8
- package/dist/assets/icons/eye.svg.js +3 -7
- package/dist/assets/icons/info-circle-fill.svg.js +3 -6
- package/dist/assets/icons/layout-sidebar.svg.js +3 -6
- package/dist/assets/icons/lightbulb-fill.svg.js +3 -6
- package/dist/assets/icons/lightbulb-off.svg.js +3 -6
- package/dist/assets/icons/lightbulb.svg.js +3 -6
- package/dist/assets/icons/mic-fill.svg.js +3 -7
- package/dist/assets/icons/mic-mute-fill.svg.js +3 -7
- package/dist/assets/icons/paperclip.svg.js +3 -6
- package/dist/assets/icons/pencil-square.svg.js +3 -7
- package/dist/assets/icons/plus-lg.svg.js +3 -6
- package/dist/assets/icons/search.svg.js +3 -6
- package/dist/assets/icons/speedometer.svg.js +3 -7
- package/dist/assets/icons/x-lg.svg.js +3 -6
- package/dist/assets/styles/dark.css.js +3 -4
- package/dist/assets/styles/light.css.js +3 -4
- package/dist/components/BaseElement.js +32 -22
- package/dist/components/BaseElement.styles.js +5 -5
- package/dist/components/alert/Alert.js +79 -51
- package/dist/components/alert/Alert.styles.js +5 -5
- package/dist/components/button/Button.js +29 -23
- package/dist/components/button/Button.styles.js +5 -5
- package/dist/components/context-menu/ContextMenu.js +123 -72
- package/dist/components/context-menu/ContextMenu.styles.js +5 -5
- package/dist/components/dialog/Dialog.js +73 -40
- package/dist/components/dialog/Dialog.styles.js +5 -5
- package/dist/components/form/Form.js +51 -32
- package/dist/components/form/Form.styles.js +5 -5
- package/dist/components/icon/Icon.js +53 -36
- package/dist/components/icon/Icon.styles.js +5 -5
- package/dist/components/icon-button/IconButton.js +44 -36
- package/dist/components/icon-button/IconButton.styles.js +5 -5
- package/dist/components/input/Input.js +161 -112
- package/dist/components/input/Input.styles.js +5 -5
- package/dist/components/menu/Menu.js +108 -58
- package/dist/components/menu/Menu.styles.js +5 -5
- package/dist/components/menu-item/MenuItem.js +67 -37
- package/dist/components/menu-item/MenuItem.styles.js +5 -5
- package/dist/components/panel/Panel.js +11 -10
- package/dist/components/panel/Panel.styles.js +5 -5
- package/dist/components/progress-ring/ProgressRing.js +70 -45
- package/dist/components/progress-ring/ProgressRing.styles.js +5 -5
- package/dist/components/spinner/Spinner.js +9 -9
- package/dist/components/spinner/Spinner.styles.js +5 -5
- package/dist/components/split-panel/SplitPanel.js +127 -66
- package/dist/components/split-panel/SplitPanel.styles.js +5 -5
- package/dist/components/split-panel/Splitter.js +29 -23
- package/dist/components/split-panel/Splitter.styles.js +5 -5
- package/dist/components/tooltip/Tooltip.js +115 -74
- package/dist/components/tooltip/Tooltip.styles.js +5 -5
- package/dist/index.js +18 -40
- package/dist/internals/attribute-converters.js +10 -8
- package/dist/internals/icons.js +41 -66
- package/dist/internals/node-helpers.js +16 -17
- package/dist/utilities/BrowserStorage.d.ts +43 -0
- package/dist/utilities/BrowserStorage.js +87 -0
- package/dist/utilities/decorators.js +19 -14
- package/dist/utilities/index.d.ts +1 -1
- package/dist/utilities/notifier.js +62 -20
- package/dist/utilities/theme.d.ts +38 -26
- package/dist/utilities/theme.js +110 -85
- package/package.json +2 -2
- package/dist/utilities/storage.d.ts +0 -60
- package/dist/utilities/storage.js +0 -120
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
class BrowserStorage {
|
|
2
|
+
constructor(options) {
|
|
3
|
+
if (window === void 0) {
|
|
4
|
+
throw new Error("BrowserStorage can only be used in a browser environment.");
|
|
5
|
+
}
|
|
6
|
+
if (options.type === "cookie" && !("cookieStore" in window)) {
|
|
7
|
+
throw new Error("Cookies are not supported in this browser.");
|
|
8
|
+
}
|
|
9
|
+
this.options = options;
|
|
10
|
+
}
|
|
11
|
+
/** 키에 prefix를 붙인 실제 저장 키 */
|
|
12
|
+
buildKey(key) {
|
|
13
|
+
return `${this.options.prefix || ""}${key}`;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 값 저장
|
|
17
|
+
* @param key 키
|
|
18
|
+
* @param value 문자열 값 (문자열이 아닌 값은 JSON.stringify 권장)
|
|
19
|
+
*/
|
|
20
|
+
async set(key, value) {
|
|
21
|
+
key = this.buildKey(key);
|
|
22
|
+
try {
|
|
23
|
+
if (this.options.type === "localStorage") {
|
|
24
|
+
window.localStorage.setItem(key, value);
|
|
25
|
+
} else if (this.options.type === "cookie") {
|
|
26
|
+
await window.cookieStore.set({
|
|
27
|
+
name: key,
|
|
28
|
+
value,
|
|
29
|
+
path: this.options.path,
|
|
30
|
+
domain: this.options.domain,
|
|
31
|
+
expires: this.options.expires,
|
|
32
|
+
sameSite: this.options.sameSite,
|
|
33
|
+
partitioned: this.options.partitioned
|
|
34
|
+
});
|
|
35
|
+
} else {
|
|
36
|
+
throw new Error(`Unsupported storage type`);
|
|
37
|
+
}
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.error("[BrowserStorage Error: set]", e);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 값 조회
|
|
44
|
+
* @param key 키
|
|
45
|
+
* @returns 값 (없으면 null)
|
|
46
|
+
*/
|
|
47
|
+
async get(key) {
|
|
48
|
+
key = this.buildKey(key);
|
|
49
|
+
try {
|
|
50
|
+
if (this.options.type === "localStorage") {
|
|
51
|
+
return window.localStorage.getItem(key);
|
|
52
|
+
} else if (this.options.type === "cookie") {
|
|
53
|
+
const item = await window.cookieStore.get({
|
|
54
|
+
name: key
|
|
55
|
+
});
|
|
56
|
+
return item?.value || null;
|
|
57
|
+
} else {
|
|
58
|
+
throw new Error(`Unsupported storage type`);
|
|
59
|
+
}
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.error("[BrowserStorage Error: get]", e);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 값 삭제
|
|
67
|
+
* @param key 키
|
|
68
|
+
*/
|
|
69
|
+
async remove(key) {
|
|
70
|
+
key = this.buildKey(key);
|
|
71
|
+
try {
|
|
72
|
+
if (this.options.type === "localStorage") {
|
|
73
|
+
window.localStorage.removeItem(key);
|
|
74
|
+
} else if (this.options.type === "cookie") {
|
|
75
|
+
await window.cookieStore.delete({
|
|
76
|
+
name: key
|
|
77
|
+
});
|
|
78
|
+
} else {
|
|
79
|
+
throw new Error(`Unsupported storage type`);
|
|
80
|
+
}
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.error("[BrowserStorage Error: remove]", e);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { BrowserStorage };
|
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
const
|
|
2
|
-
function
|
|
3
|
-
return (
|
|
4
|
-
|
|
1
|
+
const propertyMetaKey = Symbol("propertyMeta");
|
|
2
|
+
function propertyMeta(metadata) {
|
|
3
|
+
return (target, propertyKey) => {
|
|
4
|
+
setPropertyMeta(target, propertyKey, metadata);
|
|
5
5
|
};
|
|
6
6
|
}
|
|
7
|
-
function
|
|
8
|
-
|
|
7
|
+
function setPropertyMeta(target, propertyKey, metadata) {
|
|
8
|
+
metadata.name = propertyKey;
|
|
9
|
+
Reflect.defineMetadata(propertyMetaKey, metadata, target, propertyKey);
|
|
9
10
|
}
|
|
10
|
-
function
|
|
11
|
+
function getPropertyMeta(target, propertyKey) {
|
|
11
12
|
try {
|
|
12
|
-
|
|
13
|
+
target = target.prototype ? target.prototype : target;
|
|
14
|
+
if (propertyKey) {
|
|
15
|
+
return Reflect.getMetadata(propertyMetaKey, target, propertyKey);
|
|
16
|
+
} else {
|
|
17
|
+
const keys = Object.getOwnPropertyNames(target) || [];
|
|
18
|
+
const metaDataList = keys.map((key) => Reflect.getMetadata(propertyMetaKey, target, key)).filter((meta) => meta !== void 0) || [];
|
|
19
|
+
return metaDataList;
|
|
20
|
+
}
|
|
13
21
|
} catch {
|
|
14
|
-
return;
|
|
22
|
+
return void 0;
|
|
15
23
|
}
|
|
16
24
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
p as propertyMeta,
|
|
20
|
-
r as setPropertyMeta
|
|
21
|
-
};
|
|
25
|
+
|
|
26
|
+
export { getPropertyMeta, propertyMeta, setPropertyMeta };
|
|
@@ -1,29 +1,71 @@
|
|
|
1
|
-
import { Alert
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { Alert } from '../components/alert/Alert.js';
|
|
2
|
+
|
|
3
|
+
Alert.define("u-alert");
|
|
4
|
+
const notifier = {
|
|
4
5
|
container: /* @__PURE__ */ new Map(),
|
|
5
6
|
toasts: /* @__PURE__ */ new Set(),
|
|
6
7
|
/**
|
|
7
8
|
* 토스트 알림을 생성합니다.
|
|
8
9
|
*/
|
|
9
|
-
async toast(
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
async toast(options) {
|
|
11
|
+
const el = new Alert();
|
|
12
|
+
el.type = options.type;
|
|
13
|
+
el.label = options.label;
|
|
14
|
+
el.content = options.content;
|
|
15
|
+
this.toasts.add(el);
|
|
16
|
+
const position = options.position || "top-right";
|
|
17
|
+
const container = getOrCreateContainer(position);
|
|
18
|
+
container.appendChild(el);
|
|
19
|
+
await el.updateComplete;
|
|
20
|
+
el.show();
|
|
21
|
+
const duration = options.duration && options.duration > 0 ? options.duration : 3e3;
|
|
15
22
|
setTimeout(async () => {
|
|
16
|
-
await
|
|
17
|
-
|
|
23
|
+
await el.hide();
|
|
24
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
25
|
+
el.remove();
|
|
26
|
+
this.toasts.delete(el);
|
|
27
|
+
if (!container.hasChildNodes()) {
|
|
28
|
+
container.remove();
|
|
29
|
+
this.container.delete(position);
|
|
30
|
+
}
|
|
31
|
+
}, duration);
|
|
18
32
|
}
|
|
19
33
|
};
|
|
20
|
-
function
|
|
21
|
-
let
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
34
|
+
function getOrCreateContainer(position) {
|
|
35
|
+
let container = notifier.container.get(position);
|
|
36
|
+
if (container) return container;
|
|
37
|
+
container = document.createElement("div");
|
|
38
|
+
container.style.position = "fixed";
|
|
39
|
+
container.style.zIndex = "9999";
|
|
40
|
+
container.style.display = "flex";
|
|
41
|
+
container.style.gap = "10px";
|
|
42
|
+
const transformParts = [];
|
|
43
|
+
if (position.startsWith("top")) {
|
|
44
|
+
container.style.top = "20px";
|
|
45
|
+
container.style.flexDirection = "column";
|
|
46
|
+
} else if (position.startsWith("bottom")) {
|
|
47
|
+
container.style.bottom = "20px";
|
|
48
|
+
container.style.flexDirection = "column-reverse";
|
|
49
|
+
} else {
|
|
50
|
+
container.style.top = "50%";
|
|
51
|
+
container.style.flexDirection = "column";
|
|
52
|
+
transformParts.push("translateY(-50%)");
|
|
53
|
+
}
|
|
54
|
+
if (position.endsWith("left")) {
|
|
55
|
+
container.style.left = "20px";
|
|
56
|
+
container.style.alignItems = "flex-start";
|
|
57
|
+
} else if (position.endsWith("right")) {
|
|
58
|
+
container.style.right = "20px";
|
|
59
|
+
container.style.alignItems = "flex-end";
|
|
60
|
+
} else {
|
|
61
|
+
container.style.left = "50%";
|
|
62
|
+
container.style.alignItems = "center";
|
|
63
|
+
transformParts.unshift("translateX(-50%)");
|
|
64
|
+
}
|
|
65
|
+
container.style.transform = transformParts.length ? transformParts.join(" ") : "";
|
|
66
|
+
document.body.appendChild(container);
|
|
67
|
+
notifier.container.set(position, container);
|
|
68
|
+
return container;
|
|
26
69
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
};
|
|
70
|
+
|
|
71
|
+
export { notifier };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { BrowserStorageOptions } from './
|
|
1
|
+
import { BrowserStorageOptions } from './BrowserStorage.js';
|
|
2
2
|
/** iyulab에서 제공하는 스타일 타입입니다. */
|
|
3
|
-
export type ThemeType = '
|
|
3
|
+
export type ThemeType = 'system' | 'light' | 'dark';
|
|
4
4
|
/** 테마 초기 설정 옵션 */
|
|
5
5
|
export interface ThemeInitOptions {
|
|
6
6
|
/**
|
|
@@ -9,46 +9,58 @@ export interface ThemeInitOptions {
|
|
|
9
9
|
*/
|
|
10
10
|
debug?: boolean;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
* @default
|
|
12
|
+
* 내부적으로 제공되는 스타일 시트를 사용할지 여부
|
|
13
|
+
* @default true
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
useBuiltIn?: boolean;
|
|
16
16
|
/**
|
|
17
|
-
* 테마
|
|
17
|
+
* 테마 설정을 브라우저 스토리지에 영구 저장할지 여부 또는 옵션
|
|
18
18
|
* @default false
|
|
19
19
|
*/
|
|
20
|
-
|
|
20
|
+
store?: false | BrowserStorageOptions;
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
* @default
|
|
22
|
+
* 기본 테마 설정
|
|
23
|
+
* @default 'system'
|
|
24
24
|
*/
|
|
25
|
-
|
|
25
|
+
default?: ThemeType;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
28
|
+
* Theme 클래스 — 싱글톤 패턴으로 사용합니다.
|
|
29
29
|
*/
|
|
30
|
-
export declare
|
|
31
|
-
|
|
30
|
+
export declare class Theme {
|
|
31
|
+
private static _instance;
|
|
32
|
+
private readonly STORAGE_KEY_DEFAULT;
|
|
33
|
+
private storage;
|
|
34
|
+
private _isInitialized;
|
|
35
|
+
private _isDebugMode;
|
|
36
|
+
/** private 생성자 — 외부에서 new Theme()를 할 수 없게 함 */
|
|
37
|
+
private constructor();
|
|
38
|
+
/** 싱글톤 인스턴스 접근자 */
|
|
39
|
+
static get instance(): Theme;
|
|
40
|
+
/** 외부에서 현재 초기화 상태를 확인할 수 있게 getter 제공 */
|
|
41
|
+
get isInitialized(): boolean;
|
|
32
42
|
/**
|
|
33
|
-
* 테마
|
|
43
|
+
* 테마 유틸리티 초기화
|
|
34
44
|
*/
|
|
35
|
-
init(options?: ThemeInitOptions): void
|
|
45
|
+
init(options?: ThemeInitOptions): Promise<void>;
|
|
36
46
|
/**
|
|
37
|
-
* 현재
|
|
38
|
-
* @returns 현재 테마, 'light', 'dark' 또는 'system' 중 하나입니다.
|
|
47
|
+
* 현재 문서에 적용된 테마를 가져옵니다.
|
|
39
48
|
*/
|
|
40
49
|
get(): ThemeType | undefined;
|
|
41
50
|
/**
|
|
42
|
-
* 문서 테마를
|
|
43
|
-
* @param theme - 설정할 테마, 'light', 'dark' 또는 'system' 중 하나입니다.
|
|
44
|
-
* @throws 테마가 'light', 'dark' 또는 'system'이 아닌 경우 오류가 발생합니다.
|
|
51
|
+
* 현재 문서 테마를 설정합니다.
|
|
45
52
|
*/
|
|
46
53
|
set(theme: ThemeType): void;
|
|
47
54
|
/**
|
|
48
|
-
*
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
* 시스템 테마 변경을 처리하는 메서드
|
|
56
|
+
*/
|
|
57
|
+
private handleSystemThemeChanged;
|
|
58
|
+
/**
|
|
59
|
+
* 디버그 모드시 로그 출력 함수 (인스턴스 스코프)
|
|
52
60
|
*/
|
|
53
|
-
|
|
54
|
-
}
|
|
61
|
+
private log;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 테마 유틸리티 싱글톤 인스턴스입니다.
|
|
65
|
+
*/
|
|
66
|
+
export declare const theme: Theme;
|
package/dist/utilities/theme.js
CHANGED
|
@@ -1,47 +1,84 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import * as
|
|
3
|
-
import { BrowserStorage
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import * as dark from '../assets/styles/dark.css.js';
|
|
2
|
+
import * as light from '../assets/styles/light.css.js';
|
|
3
|
+
import { BrowserStorage } from './BrowserStorage.js';
|
|
4
|
+
|
|
5
|
+
class Theme {
|
|
6
|
+
/** private 생성자 — 외부에서 new Theme()를 할 수 없게 함 */
|
|
7
|
+
constructor() {
|
|
8
|
+
this.STORAGE_KEY_DEFAULT = "theme";
|
|
9
|
+
this.storage = null;
|
|
10
|
+
this._isInitialized = false;
|
|
11
|
+
this._isDebugMode = false;
|
|
12
|
+
/**
|
|
13
|
+
* 시스템 테마 변경을 처리하는 메서드
|
|
14
|
+
*/
|
|
15
|
+
this.handleSystemThemeChanged = (_) => {
|
|
16
|
+
if (document.documentElement.getAttribute("data-theme") === "system") {
|
|
17
|
+
const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
18
|
+
document.documentElement.setAttribute("theme", isDark ? "dark" : "light");
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** 싱글톤 인스턴스 접근자 */
|
|
23
|
+
static get instance() {
|
|
24
|
+
if (!this._instance) {
|
|
25
|
+
this._instance = new Theme();
|
|
26
|
+
}
|
|
27
|
+
return this._instance;
|
|
28
|
+
}
|
|
29
|
+
/** 외부에서 현재 초기화 상태를 확인할 수 있게 getter 제공 */
|
|
7
30
|
get isInitialized() {
|
|
8
|
-
return
|
|
9
|
-
}
|
|
31
|
+
return this._isInitialized;
|
|
32
|
+
}
|
|
10
33
|
/**
|
|
11
|
-
* 테마
|
|
34
|
+
* 테마 유틸리티 초기화
|
|
12
35
|
*/
|
|
13
|
-
init(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
36
|
+
async init(options) {
|
|
37
|
+
this._isDebugMode = options?.debug || false;
|
|
38
|
+
this.log("init called", { options });
|
|
39
|
+
if (options?.store) {
|
|
40
|
+
this.log("store option provided, initializing BrowserStorage");
|
|
41
|
+
this.storage = new BrowserStorage(options.store);
|
|
42
|
+
}
|
|
43
|
+
options?.useBuiltIn || true;
|
|
44
|
+
{
|
|
45
|
+
this.log("Import enabled: loading styles via internal assets");
|
|
46
|
+
const assets = Object.entries(/* #__PURE__ */ Object.assign({"../assets/styles/dark.css": dark,"../assets/styles/light.css": light
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
}));
|
|
50
|
+
this.log(`found ${assets.length} style assets`, assets.map((a) => a[0]));
|
|
51
|
+
for (let [path, module] of assets) {
|
|
52
|
+
const fileName = path.split("/").pop() || path;
|
|
53
|
+
const style = document.createElement("style");
|
|
54
|
+
style.setAttribute("data-path", fileName);
|
|
55
|
+
style.textContent = module.default;
|
|
56
|
+
if (document.head.querySelector(`style[data-path="${fileName}"]`)) {
|
|
57
|
+
this.log("style already present, skipping", fileName);
|
|
58
|
+
continue;
|
|
27
59
|
}
|
|
28
|
-
document.head.appendChild(
|
|
60
|
+
document.head.appendChild(style);
|
|
61
|
+
this.log("appended style to head", fileName);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
let theme2 = options?.default || "system";
|
|
65
|
+
if (this.storage !== null) {
|
|
66
|
+
const savedTheme = await this.storage.get(this.STORAGE_KEY_DEFAULT);
|
|
67
|
+
if (savedTheme) {
|
|
68
|
+
theme2 = savedTheme;
|
|
69
|
+
this.log("loaded theme from storage", theme2);
|
|
29
70
|
}
|
|
30
71
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
} else e?.default ? (t(`Applying default theme: ${e.default}`), a.set(e.default)) : (t("No persist option or default theme provided, applying system theme by default"), a.set("system"));
|
|
36
|
-
m = !0, t("theme initialized", { isInitialized: m });
|
|
37
|
-
},
|
|
72
|
+
this.set(theme2);
|
|
73
|
+
this._isInitialized = true;
|
|
74
|
+
this.log("theme initialized");
|
|
75
|
+
}
|
|
38
76
|
/**
|
|
39
|
-
* 현재
|
|
40
|
-
* @returns 현재 테마, 'light', 'dark' 또는 'system' 중 하나입니다.
|
|
77
|
+
* 현재 문서에 적용된 테마를 가져옵니다.
|
|
41
78
|
*/
|
|
42
79
|
get() {
|
|
43
|
-
const
|
|
44
|
-
switch (
|
|
80
|
+
const attr = document.documentElement.getAttribute("data-theme");
|
|
81
|
+
switch (attr) {
|
|
45
82
|
case "system":
|
|
46
83
|
return "system";
|
|
47
84
|
case "light":
|
|
@@ -49,60 +86,48 @@ const a = {
|
|
|
49
86
|
case "dark":
|
|
50
87
|
return "dark";
|
|
51
88
|
default:
|
|
52
|
-
return;
|
|
89
|
+
return void 0;
|
|
53
90
|
}
|
|
54
|
-
}
|
|
91
|
+
}
|
|
55
92
|
/**
|
|
56
|
-
* 문서 테마를
|
|
57
|
-
* @param theme - 설정할 테마, 'light', 'dark' 또는 'system' 중 하나입니다.
|
|
58
|
-
* @throws 테마가 'light', 'dark' 또는 'system'이 아닌 경우 오류가 발생합니다.
|
|
93
|
+
* 현재 문서 테마를 설정합니다.
|
|
59
94
|
*/
|
|
60
|
-
set(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
95
|
+
set(theme2) {
|
|
96
|
+
try {
|
|
97
|
+
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
98
|
+
if (theme2 === "system") {
|
|
99
|
+
document.documentElement.setAttribute("data-theme", "system");
|
|
100
|
+
media.addEventListener("change", this.handleSystemThemeChanged);
|
|
101
|
+
this.handleSystemThemeChanged();
|
|
102
|
+
this.log("system theme applied (data-theme=system)");
|
|
103
|
+
} else if (theme2 === "dark") {
|
|
104
|
+
media.removeEventListener("change", this.handleSystemThemeChanged);
|
|
105
|
+
document.documentElement.setAttribute("data-theme", "dark");
|
|
106
|
+
document.documentElement.setAttribute("theme", "dark");
|
|
107
|
+
this.log("dark theme applied (data-theme=dark, theme=dark)");
|
|
108
|
+
} else if (theme2 === "light") {
|
|
109
|
+
media.removeEventListener("change", this.handleSystemThemeChanged);
|
|
110
|
+
document.documentElement.setAttribute("data-theme", "light");
|
|
111
|
+
document.documentElement.setAttribute("theme", "light");
|
|
112
|
+
this.log("light theme applied (data-theme=light, theme=light)");
|
|
113
|
+
} else {
|
|
114
|
+
throw new Error(`Invalid theme: ${theme2}. Use 'light', 'dark', or 'system'.`);
|
|
71
115
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
document.documentElement.setAttribute("data-theme", "light"), document.documentElement.setAttribute("theme", "light"), t("light theme applied (data-theme=light, theme=light)");
|
|
81
|
-
} else
|
|
82
|
-
throw t("set() received invalid theme", e), new Error(`Invalid theme: ${e}. Use 'light', 'dark', or 'system'.`);
|
|
83
|
-
i && (i.set(l, e), t("persisted theme to storage", e));
|
|
84
|
-
},
|
|
116
|
+
} catch (e) {
|
|
117
|
+
this.log("Error applying theme:", e);
|
|
118
|
+
}
|
|
119
|
+
if (this.storage !== null) {
|
|
120
|
+
this.storage.set(this.STORAGE_KEY_DEFAULT, theme2);
|
|
121
|
+
this.log("saved theme to storage", theme2);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
85
124
|
/**
|
|
86
|
-
*
|
|
87
|
-
* 현재 테마가 'light'이면 'dark'로, 'dark'이면 'light'로 변경합니다.
|
|
88
|
-
* system 테마가 설정된 경우, 토글이 불가능하며 경고를 출력합니다.
|
|
89
|
-
* @return 변경된 테마입니다. system 테마인 경우 변경되지 않습니다.
|
|
125
|
+
* 디버그 모드시 로그 출력 함수 (인스턴스 스코프)
|
|
90
126
|
*/
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (t("toggle() called, current theme =", e), e === "system")
|
|
94
|
-
return console.warn("Cannot toggle theme when system theme is active. Please set a specific theme (light or dark) first."), t("toggle aborted: current theme is system"), e;
|
|
95
|
-
const s = e === "light" ? "dark" : "light";
|
|
96
|
-
return a.set(s), t("toggle completed, new theme =", s), s;
|
|
97
|
-
}
|
|
98
|
-
}, n = () => {
|
|
99
|
-
if (document.documentElement.getAttribute("data-theme") === "system") {
|
|
100
|
-
const e = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
101
|
-
document.documentElement.setAttribute("theme", e ? "dark" : "light");
|
|
127
|
+
log(...args) {
|
|
128
|
+
if (this._isDebugMode) console.log("[theme]", ...args);
|
|
102
129
|
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
export {
|
|
107
|
-
a as theme
|
|
108
|
-
};
|
|
130
|
+
}
|
|
131
|
+
const theme = Theme.instance;
|
|
132
|
+
|
|
133
|
+
export { Theme, theme };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iyulab/components",
|
|
3
3
|
"description": "web-components library by iyulab based on lit-element",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"iyulab",
|
|
7
7
|
"web-components",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"import": "./dist/index.js"
|
|
29
29
|
},
|
|
30
30
|
"./dist/*": {
|
|
31
|
-
"types": "./dist
|
|
31
|
+
"types": "./dist/*.d.ts",
|
|
32
32
|
"import": "./dist/*"
|
|
33
33
|
}
|
|
34
34
|
},
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
export type StorageMode = 'localStorage' | 'cookie';
|
|
2
|
-
export interface LocalStorageOptions {
|
|
3
|
-
type: 'localStorage';
|
|
4
|
-
}
|
|
5
|
-
export interface CookieOptions {
|
|
6
|
-
type: 'cookie';
|
|
7
|
-
/** 만료: Date | milliseconds timestamp | ISO string | days (number) */
|
|
8
|
-
expires?: Date | number | string;
|
|
9
|
-
path?: string;
|
|
10
|
-
domain?: string;
|
|
11
|
-
sameSite?: 'Lax' | 'Strict' | 'None';
|
|
12
|
-
secure?: boolean;
|
|
13
|
-
}
|
|
14
|
-
export type BrowserStorageOptions = (LocalStorageOptions | CookieOptions) & {
|
|
15
|
-
/** 키 앞에 붙일 접두사 (옵션) */
|
|
16
|
-
prefix?: string;
|
|
17
|
-
};
|
|
18
|
-
/**
|
|
19
|
-
* BrowserStorage - localStorage / cookie 래퍼
|
|
20
|
-
*
|
|
21
|
-
* 사용:
|
|
22
|
-
* const s = new BrowserStorage({ type: 'localStorage', prefix: 'app' });
|
|
23
|
-
* s.set('token', 'abc');
|
|
24
|
-
* s.get('token'); // 'abc'
|
|
25
|
-
*
|
|
26
|
-
* const c = new BrowserStorage({ type: 'cookie', prefix: 'app', expires: 7 }); // expires = 7일
|
|
27
|
-
* c.set('lang', 'ko');
|
|
28
|
-
*/
|
|
29
|
-
export declare class BrowserStorage {
|
|
30
|
-
private mode;
|
|
31
|
-
private prefix;
|
|
32
|
-
private cookieOpts;
|
|
33
|
-
constructor(options: BrowserStorageOptions);
|
|
34
|
-
/** 키에 prefix를 붙인 실제 저장 키 */
|
|
35
|
-
private buildKey;
|
|
36
|
-
/**
|
|
37
|
-
* 값 저장
|
|
38
|
-
* @param key 키
|
|
39
|
-
* @param value 문자열 값 (문자열이 아닌 값은 JSON.stringify 권장)
|
|
40
|
-
*/
|
|
41
|
-
set(key: string, value: string): void;
|
|
42
|
-
/**
|
|
43
|
-
* 값 조회
|
|
44
|
-
* @param key 키
|
|
45
|
-
* @returns 값 (없으면 null)
|
|
46
|
-
*/
|
|
47
|
-
get(key: string): string | null;
|
|
48
|
-
/**
|
|
49
|
-
* 키 제거
|
|
50
|
-
*/
|
|
51
|
-
remove(key: string): void;
|
|
52
|
-
/**
|
|
53
|
-
* 현재 prefix 기준으로 모든 항목 제거 (주의: localStorage 전체 삭제 아님)
|
|
54
|
-
*/
|
|
55
|
-
clear(): void;
|
|
56
|
-
/**
|
|
57
|
-
* 모든 키 목록 반환 (prefix 적용된 실제 키들에서 접두사 제거한 상태)
|
|
58
|
-
*/
|
|
59
|
-
keys(): string[];
|
|
60
|
-
}
|